diff --git a/nginx/conf.d/header.conf b/nginx/conf.d/header.conf index 0ffa54e9..59deea39 100644 --- a/nginx/conf.d/header.conf +++ b/nginx/conf.d/header.conf @@ -1,9 +1,9 @@ proxy_pass_header Server; proxy_http_version 1.1; proxy_set_header Host $http_host; -proxy_set_header Connection $connection_upgrade; -proxy_set_header Upgrade $http_upgrade; +proxy_set_header Connection $connection_upgrade; +proxy_set_header Upgrade $http_upgrade; proxy_set_header X-Real-IP $remote_addr; -proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; -proxy_set_header X-Forwarded-Proto $scheme; +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Proto $scheme; diff --git a/nginx/templates/http-server.template b/nginx/templates/http-server.template index 8b7a1f1e..f4c91d91 100644 --- a/nginx/templates/http-server.template +++ b/nginx/templates/http-server.template @@ -1,6 +1,6 @@ server { - listen 80; - server_name ${SERVER_DOMAIN}; + listen 80; + server_name ${SERVER_DOMAIN}; location / { return 301 https://$http_host$request_uri; diff --git a/nginx/templates/ssl-server.template b/nginx/templates/ssl-server.template index 3ed5f615..8bd2f677 100644 --- a/nginx/templates/ssl-server.template +++ b/nginx/templates/ssl-server.template @@ -1,13 +1,13 @@ server { listen 443 ssl; - server_name ${SERVER_DOMAIN}; + server_name ${SERVER_DOMAIN}; - ssl_certificate /etc/letsencrypt/live/${SERVER_DOMAIN}/fullchain.pem; + ssl_certificate /etc/letsencrypt/live/${SERVER_DOMAIN}/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/${SERVER_DOMAIN}/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; - location / { + location / { proxy_pass http://backend; } } diff --git a/src/docs/asciidoc/coupon.adoc b/src/docs/asciidoc/coupon.adoc index 6e98b9e4..4f424964 100644 --- a/src/docs/asciidoc/coupon.adoc +++ b/src/docs/asciidoc/coupon.adoc @@ -85,6 +85,15 @@ include::{snippets}/coupons/http-response.adoc[] 사용자가 자신의 보관함에 있는 쿠폰들을 조회합니다. +==== 요청 + +include::{snippets}/my-coupons/couponId/http-request.adoc[] + +[discrete] +==== 응답 + +include::{snippets}/my-coupons/couponId/http-response.adoc[] + --- === 쿠폰 사용 (진행 중) diff --git a/src/main/java/com/moabam/api/application/bug/BugService.java b/src/main/java/com/moabam/api/application/bug/BugService.java index 9b0e88c3..69c5f548 100644 --- a/src/main/java/com/moabam/api/application/bug/BugService.java +++ b/src/main/java/com/moabam/api/application/bug/BugService.java @@ -55,7 +55,7 @@ public PurchaseProductResponse purchaseBugProduct(Long memberId, Long productId, Payment payment = PaymentMapper.toPayment(memberId, product); if (!isNull(request.couponWalletId())) { - Coupon coupon = couponService.getByWallet(memberId, request.couponWalletId()); + Coupon coupon = couponService.getByWalletIdAndMemberId(request.couponWalletId(), memberId); payment.applyCoupon(coupon, request.couponWalletId()); } paymentRepository.save(payment); diff --git a/src/main/java/com/moabam/api/application/coupon/CouponManageService.java b/src/main/java/com/moabam/api/application/coupon/CouponManageService.java new file mode 100644 index 00000000..f4d1ef92 --- /dev/null +++ b/src/main/java/com/moabam/api/application/coupon/CouponManageService.java @@ -0,0 +1,91 @@ +package com.moabam.api.application.coupon; + +import java.time.LocalDate; +import java.util.Optional; +import java.util.Set; + +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import com.moabam.api.domain.coupon.Coupon; +import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.api.domain.coupon.repository.CouponManageRepository; +import com.moabam.api.domain.coupon.repository.CouponRepository; +import com.moabam.api.domain.coupon.repository.CouponWalletRepository; +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 lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CouponManageService { + + private static final long ISSUE_SIZE = 10; + + private final ClockHolder clockHolder; + + private final CouponRepository couponRepository; + private final CouponManageRepository couponManageRepository; + private final CouponWalletRepository couponWalletRepository; + + @Scheduled(fixedDelay = 1000) + public void issue() { + LocalDate now = clockHolder.date(); + Optional isCoupon = couponRepository.findByStartAt(now); + + if (!canIssue(isCoupon)) { + return; + } + + Coupon coupon = isCoupon.get(); + Set membersId = couponManageRepository.popMinQueue(coupon.getName(), ISSUE_SIZE); + + membersId.forEach(memberId -> { + int nextStock = couponManageRepository.increaseIssuedStock(coupon.getName()); + + if (coupon.getStock() < nextStock) { + return; + } + + CouponWallet couponWallet = CouponWallet.create(memberId, coupon); + couponWalletRepository.save(couponWallet); + }); + } + + public void register(AuthMember authMember, String couponName) { + double registerTime = System.currentTimeMillis(); + validateRegister(couponName); + couponManageRepository.addIfAbsentQueue(couponName, authMember.id(), registerTime); + } + + public void deleteCouponManage(String couponName) { + couponManageRepository.deleteQueue(couponName); + couponManageRepository.deleteIssuedStock(couponName); + } + + private void validateRegister(String couponName) { + LocalDate now = clockHolder.date(); + Optional coupon = couponRepository.findByStartAt(now); + + if (coupon.isEmpty() || !coupon.get().getName().equals(couponName)) { + throw new BadRequestException(ErrorMessage.INVALID_COUPON_PERIOD); + } + } + + private boolean canIssue(Optional coupon) { + if (coupon.isEmpty()) { + return false; + } + + Coupon currentCoupon = coupon.get(); + int currentStock = couponManageRepository.getIssuedStock(currentCoupon.getName()); + int maxStock = currentCoupon.getStock(); + + return currentStock < maxStock; + } +} diff --git a/src/main/java/com/moabam/api/application/coupon/CouponMapper.java b/src/main/java/com/moabam/api/application/coupon/CouponMapper.java index d38dbb77..ef8eb15a 100644 --- a/src/main/java/com/moabam/api/application/coupon/CouponMapper.java +++ b/src/main/java/com/moabam/api/application/coupon/CouponMapper.java @@ -1,9 +1,14 @@ package com.moabam.api.application.coupon; +import java.util.List; + import com.moabam.api.domain.coupon.Coupon; import com.moabam.api.domain.coupon.CouponType; +import com.moabam.api.domain.coupon.CouponWallet; import com.moabam.api.dto.coupon.CouponResponse; import com.moabam.api.dto.coupon.CreateCouponRequest; +import com.moabam.api.dto.coupon.MyCouponResponse; +import com.moabam.global.common.util.StreamUtils; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -25,7 +30,7 @@ public static Coupon toEntity(Long adminId, CreateCouponRequest coupon) { } // TODO : Admin Table 생성 시, 관리자 명 추가할 예정 - public static CouponResponse toDto(Coupon coupon) { + public static CouponResponse toResponse(Coupon coupon) { return CouponResponse.builder() .id(coupon.getId()) .adminName(coupon.getAdminId() + "admin") @@ -38,4 +43,24 @@ public static CouponResponse toDto(Coupon coupon) { .openAt(coupon.getOpenAt()) .build(); } + + public static List toResponses(List coupons) { + return StreamUtils.map(coupons, CouponMapper::toResponse); + } + + public static MyCouponResponse toMyResponse(CouponWallet couponWallet) { + Coupon coupon = couponWallet.getCoupon(); + + return MyCouponResponse.builder() + .id(coupon.getId()) + .name(coupon.getName()) + .description(coupon.getDescription()) + .point(coupon.getPoint()) + .type(coupon.getType()) + .build(); + } + + public static List toMyResponses(List couponWallets) { + return StreamUtils.map(couponWallets, CouponMapper::toMyResponse); + } } diff --git a/src/main/java/com/moabam/api/application/coupon/CouponQueueService.java b/src/main/java/com/moabam/api/application/coupon/CouponQueueService.java deleted file mode 100644 index 2d0c62e9..00000000 --- a/src/main/java/com/moabam/api/application/coupon/CouponQueueService.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.moabam.api.application.coupon; - -import org.springframework.stereotype.Service; - -import com.moabam.api.domain.coupon.Coupon; -import com.moabam.api.domain.coupon.repository.CouponQueueRepository; -import com.moabam.global.auth.model.AuthMember; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Service -@RequiredArgsConstructor -public class CouponQueueService { - - private final CouponService couponService; - private final CouponQueueRepository couponQueueRepository; - - public void register(AuthMember authMember, String couponName) { - double registerTime = System.currentTimeMillis(); - - if (canRegister(couponName)) { - log.info("{} 쿠폰이 모두 발급되었습니다.", couponName); - return; - } - - couponQueueRepository.addIfAbsent(couponName, authMember.nickname(), registerTime); - } - - private boolean canRegister(String couponName) { - Coupon coupon = couponService.validatePeriod(couponName); - - return coupon.getStock() <= couponQueueRepository.size(coupon.getName()); - } -} diff --git a/src/main/java/com/moabam/api/application/coupon/CouponService.java b/src/main/java/com/moabam/api/application/coupon/CouponService.java index fc14a1e2..fb153e08 100644 --- a/src/main/java/com/moabam/api/application/coupon/CouponService.java +++ b/src/main/java/com/moabam/api/application/coupon/CouponService.java @@ -7,13 +7,16 @@ import org.springframework.transaction.annotation.Transactional; import com.moabam.api.domain.coupon.Coupon; +import com.moabam.api.domain.coupon.CouponWallet; import com.moabam.api.domain.coupon.repository.CouponRepository; import com.moabam.api.domain.coupon.repository.CouponSearchRepository; import com.moabam.api.domain.coupon.repository.CouponWalletRepository; +import com.moabam.api.domain.coupon.repository.CouponWalletSearchRepository; import com.moabam.api.domain.member.Role; import com.moabam.api.dto.coupon.CouponResponse; import com.moabam.api.dto.coupon.CouponStatusRequest; import com.moabam.api.dto.coupon.CreateCouponRequest; +import com.moabam.api.dto.coupon.MyCouponResponse; import com.moabam.global.auth.model.AuthMember; import com.moabam.global.common.util.ClockHolder; import com.moabam.global.error.exception.BadRequestException; @@ -28,15 +31,18 @@ @Transactional(readOnly = true) public class CouponService { + private final ClockHolder clockHolder; + private final CouponManageService couponManageService; private final CouponRepository couponRepository; private final CouponSearchRepository couponSearchRepository; private final CouponWalletRepository couponWalletRepository; - private final ClockHolder clockHolder; + private final CouponWalletSearchRepository couponWalletSearchRepository; @Transactional public void create(AuthMember admin, CreateCouponRequest request) { validateAdminRole(admin); validateConflictName(request.name()); + validateConflictStartAt(request.startAt()); validatePeriod(request.startAt(), request.openAt()); Coupon coupon = CouponMapper.toEntity(admin.id(), request); @@ -49,6 +55,7 @@ public void delete(AuthMember admin, Long couponId) { Coupon coupon = couponRepository.findById(couponId) .orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON)); couponRepository.delete(coupon); + couponManageService.deleteCouponManage(coupon.getName()); } @Transactional @@ -61,7 +68,7 @@ public CouponResponse getById(Long couponId) { Coupon coupon = couponRepository.findById(couponId) .orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON)); - return CouponMapper.toDto(coupon); + return CouponMapper.toResponse(coupon); } public Coupon getByWallet(Long memberId, Long couponWalletId) { @@ -71,28 +78,27 @@ public Coupon getByWallet(Long memberId, Long couponWalletId) { } public List getAllByStatus(CouponStatusRequest request) { - LocalDate now = LocalDate.from(clockHolder.times()); + LocalDate now = clockHolder.date(); List coupons = couponSearchRepository.findAllByStatus(now, request); - return coupons.stream() - .map(CouponMapper::toDto) - .toList(); + return CouponMapper.toResponses(coupons); } - public Coupon validatePeriod(String couponName) { - LocalDate now = LocalDate.from(clockHolder.times()); - Coupon coupon = couponRepository.findByName(couponName) - .orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON)); + public List getWallet(Long couponId, AuthMember authMember) { + List couponWallets = + couponWalletSearchRepository.findAllByCouponIdAndMemberId(couponId, authMember.id()); - if (!now.equals(coupon.getStartAt())) { - throw new BadRequestException(ErrorMessage.INVALID_COUPON_PERIOD); - } + return CouponMapper.toMyResponses(couponWallets); + } - return coupon; + public Coupon getByWalletIdAndMemberId(Long couponWalletId, Long memberId) { + return couponWalletSearchRepository.findByIdAndMemberId(couponWalletId, memberId) + .orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON_WALLET)) + .getCoupon(); } private void validatePeriod(LocalDate startAt, LocalDate openAt) { - LocalDate now = LocalDate.from(clockHolder.times()); + LocalDate now = clockHolder.date(); if (!now.isBefore(startAt)) { throw new BadRequestException(ErrorMessage.INVALID_COUPON_START_AT_PERIOD); @@ -114,4 +120,10 @@ private void validateConflictName(String couponName) { throw new ConflictException(ErrorMessage.CONFLICT_COUPON_NAME); } } + + private void validateConflictStartAt(LocalDate startAt) { + if (couponRepository.existsByStartAt(startAt)) { + throw new ConflictException(ErrorMessage.CONFLICT_COUPON_START_AT); + } + } } 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..b54bdfc6 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.findDefaultSkin(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/application/notification/NotificationService.java b/src/main/java/com/moabam/api/application/notification/NotificationService.java index 11f408e4..bcbec2af 100644 --- a/src/main/java/com/moabam/api/application/notification/NotificationService.java +++ b/src/main/java/com/moabam/api/application/notification/NotificationService.java @@ -30,24 +30,22 @@ public class NotificationService { private static final String KNOCK_BODY = "%s님이 콕 찔렀습니다."; private static final String CERTIFY_TIME_BODY = "%s방 인증 시간입니다."; - private static final String KNOCK_KEY = "room_%s_member_%s_knocks_%s"; + private final ClockHolder clockHolder; private final FcmService fcmService; private final RoomService roomService; + private final NotificationRepository notificationRepository; private final ParticipantSearchRepository participantSearchRepository; - private final ClockHolder clockHolder; @Transactional public void sendKnock(AuthMember member, Long targetId, Long roomId) { roomService.validateRoomById(roomId); - - String knockKey = generateKnockKey(member.id(), targetId, roomId); - validateConflictKnock(knockKey); + validateConflictKnock(member.id(), targetId, roomId); String fcmToken = fcmService.findTokenByMemberId(targetId); fcmService.sendAsync(fcmToken, String.format(KNOCK_BODY, member.nickname())); - notificationRepository.saveKnock(knockKey); + notificationRepository.saveKnock(member.id(), targetId, roomId); } @Scheduled(cron = "0 50 * * * *") @@ -68,8 +66,8 @@ public List getMyKnockStatusInRoom(Long memberId, Long roomId, List !participant.getMemberId().equals(memberId)) .toList(); - Predicate knockPredicate = targetId - -> notificationRepository.existsKnockByKey(generateKnockKey(memberId, targetId, roomId)); + Predicate knockPredicate = targetId -> + notificationRepository.existsKnockByKey(memberId, targetId, roomId); Map> knockStatus = filteredParticipants.stream() .map(Participant::getMemberId) @@ -78,13 +76,9 @@ public List getMyKnockStatusInRoom(Long memberId, Long roomId, List multipartFiles) { - LocalDate today = LocalDate.now(); + public void certifyRoom(Long memberId, Long roomId, List imageUrls) { + LocalDate today = clockHolder.date(); Participant participant = participantSearchRepository.findOne(memberId, roomId) .orElseThrow(() -> new NotFoundException(PARTICIPANT_NOT_FOUND)); Room room = participant.getRoom(); @@ -70,47 +71,17 @@ public void certifyRoom(Long memberId, Long roomId, List multipar validateCertifyTime(clockHolder.times(), room.getCertifyTime()); validateAlreadyCertified(memberId, roomId, today); - DailyMemberCertification dailyMemberCertification = CertificationsMapper.toDailyMemberCertification(memberId, - roomId, participant); - dailyMemberCertificationRepository.save(dailyMemberCertification); - - member.increaseTotalCertifyCount(); - participant.updateCertifyCount(); - - List result = imageService.uploadImages(multipartFiles, CERTIFICATION); - saveNewCertifications(result, memberId); + certifyMember(memberId, roomId, participant, member, imageUrls); Optional dailyRoomCertification = certificationsSearchRepository.findDailyRoomCertification(roomId, today); if (dailyRoomCertification.isEmpty()) { - List dailyMemberCertifications = - certificationsSearchRepository.findSortedDailyMemberCertifications(roomId, today); - double completePercentage = calculateCompletePercentage(dailyMemberCertifications.size(), - room.getCurrentUserCount()); - - if (completePercentage >= 75) { - DailyRoomCertification createDailyRoomCertification = CertificationsMapper.toDailyRoomCertification( - roomId, today); - - dailyRoomCertificationRepository.save(createDailyRoomCertification); - - int expAppliedRoomLevel = getRoomLevelAfterExpApply(roomLevel, room); - - List memberIds = dailyMemberCertifications.stream() - .map(DailyMemberCertification::getMemberId) - .toList(); - - memberService.getRoomMembers(memberIds) - .forEach(completedMember -> completedMember.getBug().increaseBug(bugType, expAppliedRoomLevel)); - - return; - } + certifyRoomIfAvailable(roomId, today, room, bugType, roomLevel); + return; } - if (dailyRoomCertification.isPresent()) { - member.getBug().increaseBug(bugType, roomLevel); - } + member.getBug().increaseBug(bugType, roomLevel); } public boolean existsMemberCertification(Long memberId, Long roomId, LocalDate date) { @@ -138,7 +109,17 @@ private void validateAlreadyCertified(Long memberId, Long roomId, LocalDate toda } } - private void saveNewCertifications(List imageUrls, Long memberId) { + private void certifyMember(Long memberId, Long roomId, Participant participant, Member member, List urls) { + DailyMemberCertification dailyMemberCertification = CertificationsMapper.toDailyMemberCertification(memberId, + roomId, participant); + dailyMemberCertificationRepository.save(dailyMemberCertification); + member.increaseTotalCertifyCount(); + participant.updateCertifyCount(); + + saveNewCertifications(memberId, urls); + } + + private void saveNewCertifications(Long memberId, List imageUrls) { List certifications = new ArrayList<>(); for (String imageUrl : imageUrls) { @@ -153,6 +134,23 @@ private void saveNewCertifications(List imageUrls, Long memberId) { certificationRepository.saveAll(certifications); } + private void certifyRoomIfAvailable(Long roomId, LocalDate today, Room room, BugType bugType, int roomLevel) { + List dailyMemberCertifications = + certificationsSearchRepository.findSortedDailyMemberCertifications(roomId, today); + double completePercentage = calculateCompletePercentage(dailyMemberCertifications.size(), + room.getCurrentUserCount()); + + if (completePercentage >= REQUIRED_ROOM_CERTIFICATION) { + DailyRoomCertification createDailyRoomCertification = CertificationsMapper.toDailyRoomCertification( + roomId, today); + + dailyRoomCertificationRepository.save(createDailyRoomCertification); + int expAppliedRoomLevel = getRoomLevelAfterExpApply(roomLevel, room); + + provideBugToCompletedMembers(bugType, dailyMemberCertifications, expAppliedRoomLevel); + } + } + private double calculateCompletePercentage(int certifiedMembersCount, int currentsMembersCount) { double completePercentage = ((double)certifiedMembersCount / currentsMembersCount) * 100; @@ -169,4 +167,14 @@ private int getRoomLevelAfterExpApply(int roomLevel, Room room) { return room.getLevel(); } + + private void provideBugToCompletedMembers(BugType bugType, List dailyMemberCertifications, + int expAppliedRoomLevel) { + List memberIds = dailyMemberCertifications.stream() + .map(DailyMemberCertification::getMemberId) + .toList(); + + memberService.getRoomMembers(memberIds) + .forEach(completedMember -> completedMember.getBug().increaseBug(bugType, expAppliedRoomLevel)); + } } diff --git a/src/main/java/com/moabam/api/application/room/RoomService.java b/src/main/java/com/moabam/api/application/room/RoomService.java index 4775031f..50592ead 100644 --- a/src/main/java/com/moabam/api/application/room/RoomService.java +++ b/src/main/java/com/moabam/api/application/room/RoomService.java @@ -1,7 +1,14 @@ package com.moabam.api.application.room; -import static com.moabam.api.domain.room.RoomType.*; -import static com.moabam.global.error.model.ErrorMessage.*; +import static com.moabam.api.domain.room.RoomType.MORNING; +import static com.moabam.api.domain.room.RoomType.NIGHT; +import static com.moabam.global.error.model.ErrorMessage.MEMBER_ROOM_EXCEED; +import static com.moabam.global.error.model.ErrorMessage.PARTICIPANT_NOT_FOUND; +import static com.moabam.global.error.model.ErrorMessage.ROOM_EXIT_MANAGER_FAIL; +import static com.moabam.global.error.model.ErrorMessage.ROOM_MAX_USER_REACHED; +import static com.moabam.global.error.model.ErrorMessage.ROOM_MODIFY_UNAUTHORIZED_REQUEST; +import static com.moabam.global.error.model.ErrorMessage.ROOM_NOT_FOUND; +import static com.moabam.global.error.model.ErrorMessage.WRONG_ROOM_PASSWORD; import java.util.List; @@ -10,6 +17,7 @@ import org.springframework.transaction.annotation.Transactional; import com.moabam.api.application.member.MemberService; +import com.moabam.api.application.room.mapper.ParticipantMapper; import com.moabam.api.application.room.mapper.RoomMapper; import com.moabam.api.application.room.mapper.RoutineMapper; import com.moabam.api.domain.member.Member; @@ -21,7 +29,6 @@ import com.moabam.api.domain.room.repository.ParticipantSearchRepository; import com.moabam.api.domain.room.repository.RoomRepository; import com.moabam.api.domain.room.repository.RoutineRepository; -import com.moabam.api.domain.room.repository.RoutineSearchRepository; import com.moabam.api.dto.room.CreateRoomRequest; import com.moabam.api.dto.room.EnterRoomRequest; import com.moabam.api.dto.room.ModifyRoomRequest; @@ -38,7 +45,6 @@ public class RoomService { private final RoomRepository roomRepository; private final RoutineRepository routineRepository; - private final RoutineSearchRepository routineSearchRepository; private final ParticipantRepository participantRepository; private final ParticipantSearchRepository participantSearchRepository; private final MemberService memberService; @@ -47,19 +53,15 @@ public class RoomService { public Long createRoom(Long memberId, String nickname, CreateRoomRequest createRoomRequest) { Room room = RoomMapper.toRoomEntity(createRoomRequest); List routines = RoutineMapper.toRoutineEntities(room, createRoomRequest.routines()); - Participant participant = Participant.builder() - .room(room) - .memberId(memberId) - .build(); + Participant participant = ParticipantMapper.toParticipant(room, memberId); - if (!isEnterRoomAvailable(memberId, room.getRoomType())) { - throw new BadRequestException(MEMBER_ROOM_EXCEED); - } - - increaseRoomCount(memberId, room.getRoomType()); + validateEnteredRoomCount(memberId, room.getRoomType()); + Member member = memberService.getById(memberId); + member.enterRoom(room.getRoomType()); participant.enableManager(); room.changeManagerNickname(nickname); + Room savedRoom = roomRepository.save(room); routineRepository.saveAll(routines); participantRepository.save(participant); @@ -79,7 +81,7 @@ public void modifyRoom(Long memberId, Long roomId, ModifyRoomRequest modifyRoomR room.changeCertifyTime(modifyRoomRequest.certifyTime()); room.changeMaxCount(modifyRoomRequest.maxUserCount()); - List routines = routineSearchRepository.findAllByRoomId(roomId); + List routines = routineRepository.findAllByRoomId(roomId); routineRepository.deleteAll(routines); List newRoutines = RoutineMapper.toRoutineEntities(room, modifyRoomRequest.routines()); @@ -91,13 +93,11 @@ public void enterRoom(Long memberId, Long roomId, EnterRoomRequest enterRoomRequ Room room = roomRepository.findById(roomId).orElseThrow(() -> new NotFoundException(ROOM_NOT_FOUND)); validateRoomEnter(memberId, enterRoomRequest.password(), room); + Member member = memberService.getById(memberId); + member.enterRoom(room.getRoomType()); room.increaseCurrentUserCount(); - increaseRoomCount(memberId, room.getRoomType()); - Participant participant = Participant.builder() - .room(room) - .memberId(memberId) - .build(); + Participant participant = ParticipantMapper.toParticipant(room, memberId); participantRepository.save(participant); } @@ -106,11 +106,11 @@ public void exitRoom(Long memberId, Long roomId) { Participant participant = getParticipant(memberId, roomId); Room room = participant.getRoom(); - if (participant.isManager() && room.getCurrentUserCount() != 1) { - throw new BadRequestException(ROOM_EXIT_MANAGER_FAIL); - } + validateRoomExit(participant, room); + + Member member = memberService.getById(memberId); + member.exitRoom(room.getRoomType()); - decreaseRoomCount(memberId, room.getRoomType()); participant.removeRoom(); participantRepository.flush(); participantRepository.delete(participant); @@ -124,7 +124,7 @@ public void exitRoom(Long memberId, Long roomId) { } @Transactional - public void mandateRoomManager(Long managerId, Long roomId, Long memberId) { + public void mandateManager(Long managerId, Long roomId, Long memberId) { Participant managerParticipant = getParticipant(managerId, roomId); Participant memberParticipant = getParticipant(memberId, roomId); validateManagerAuthorization(managerParticipant); @@ -141,13 +141,14 @@ public void mandateRoomManager(Long managerId, Long roomId, Long memberId) { public void deportParticipant(Long managerId, Long roomId, Long memberId) { Participant managerParticipant = getParticipant(managerId, roomId); Participant memberParticipant = getParticipant(memberId, roomId); - Room room = managerParticipant.getRoom(); - validateManagerAuthorization(managerParticipant); + Room room = managerParticipant.getRoom(); participantRepository.delete(memberParticipant); room.decreaseCurrentUserCount(); - decreaseRoomCount(memberId, room.getRoomType()); + + Member member = memberService.getById(memberId); + member.exitRoom(room.getRoomType()); } public void validateRoomById(Long roomId) { @@ -168,9 +169,8 @@ private void validateManagerAuthorization(Participant participant) { } private void validateRoomEnter(Long memberId, String requestPassword, Room room) { - if (!isEnterRoomAvailable(memberId, room.getRoomType())) { - throw new BadRequestException(MEMBER_ROOM_EXCEED); - } + validateEnteredRoomCount(memberId, room.getRoomType()); + if (!StringUtils.isEmpty(requestPassword) && !room.getPassword().equals(requestPassword)) { throw new BadRequestException(WRONG_ROOM_PASSWORD); } @@ -179,38 +179,20 @@ private void validateRoomEnter(Long memberId, String requestPassword, Room room) } } - private boolean isEnterRoomAvailable(Long memberId, RoomType roomType) { + private void validateEnteredRoomCount(Long memberId, RoomType roomType) { Member member = memberService.getById(memberId); if (roomType.equals(MORNING) && member.getCurrentMorningCount() >= 3) { - return false; + throw new BadRequestException(MEMBER_ROOM_EXCEED); } if (roomType.equals(NIGHT) && member.getCurrentNightCount() >= 3) { - return false; - } - - return true; - } - - private void increaseRoomCount(Long memberId, RoomType roomType) { - Member member = memberService.getById(memberId); - - if (roomType.equals(MORNING)) { - member.enterMorningRoom(); - return; + throw new BadRequestException(MEMBER_ROOM_EXCEED); } - - member.enterNightRoom(); } - private void decreaseRoomCount(Long memberId, RoomType roomType) { - Member member = memberService.getById(memberId); - - if (roomType.equals(MORNING)) { - member.exitMorningRoom(); - return; + private void validateRoomExit(Participant participant, Room room) { + if (participant.isManager() && room.getCurrentUserCount() != 1) { + throw new BadRequestException(ROOM_EXIT_MANAGER_FAIL); } - - member.exitNightRoom(); } } diff --git a/src/main/java/com/moabam/api/application/room/RoomSearchService.java b/src/main/java/com/moabam/api/application/room/SearchService.java similarity index 61% rename from src/main/java/com/moabam/api/application/room/RoomSearchService.java rename to src/main/java/com/moabam/api/application/room/SearchService.java index fad7eaa8..73ee5cb9 100644 --- a/src/main/java/com/moabam/api/application/room/RoomSearchService.java +++ b/src/main/java/com/moabam/api/application/room/SearchService.java @@ -1,8 +1,12 @@ package com.moabam.api.application.room; -import static com.moabam.global.common.util.GlobalConstant.*; -import static com.moabam.global.error.model.ErrorMessage.*; -import static org.apache.commons.lang3.StringUtils.*; +import static com.moabam.global.common.util.GlobalConstant.NOT_COMPLETED_RANK; +import static com.moabam.global.common.util.GlobalConstant.ROOM_FIXED_SEARCH_SIZE; +import static com.moabam.global.error.model.ErrorMessage.INVENTORY_NOT_FOUND; +import static com.moabam.global.error.model.ErrorMessage.PARTICIPANT_NOT_FOUND; +import static com.moabam.global.error.model.ErrorMessage.ROOM_DETAILS_ERROR; +import static com.moabam.global.error.model.ErrorMessage.ROOM_MODIFY_UNAUTHORIZED_REQUEST; +import static org.apache.commons.lang3.StringUtils.isEmpty; import java.time.LocalDate; import java.time.Period; @@ -19,6 +23,8 @@ import com.moabam.api.application.room.mapper.ParticipantMapper; import com.moabam.api.application.room.mapper.RoomMapper; import com.moabam.api.application.room.mapper.RoutineMapper; +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.room.Certification; import com.moabam.api.domain.room.DailyMemberCertification; @@ -31,8 +37,11 @@ import com.moabam.api.domain.room.repository.ParticipantSearchRepository; import com.moabam.api.domain.room.repository.RoomRepository; import com.moabam.api.domain.room.repository.RoomSearchRepository; -import com.moabam.api.domain.room.repository.RoutineSearchRepository; +import com.moabam.api.domain.room.repository.RoutineRepository; import com.moabam.api.dto.room.CertificationImageResponse; +import com.moabam.api.dto.room.CertificationImagesResponse; +import com.moabam.api.dto.room.GetAllRoomResponse; +import com.moabam.api.dto.room.GetAllRoomsResponse; import com.moabam.api.dto.room.ManageRoomResponse; import com.moabam.api.dto.room.MyRoomResponse; import com.moabam.api.dto.room.MyRoomsResponse; @@ -41,8 +50,6 @@ import com.moabam.api.dto.room.RoomHistoryResponse; import com.moabam.api.dto.room.RoomsHistoryResponse; import com.moabam.api.dto.room.RoutineResponse; -import com.moabam.api.dto.room.SearchAllRoomResponse; -import com.moabam.api.dto.room.SearchAllRoomsResponse; import com.moabam.api.dto.room.TodayCertificateRankResponse; import com.moabam.global.common.util.ClockHolder; import com.moabam.global.error.exception.ForbiddenException; @@ -54,15 +61,16 @@ @Service @RequiredArgsConstructor @Transactional(readOnly = true) -public class RoomSearchService { +public class SearchService { - private final CertificationsSearchRepository certificationsSearchRepository; - private final ParticipantSearchRepository participantSearchRepository; - private final RoutineSearchRepository routineSearchRepository; - private final RoomSearchRepository roomSearchRepository; private final RoomRepository roomRepository; + private final RoomSearchRepository roomSearchRepository; + private final RoutineRepository routineRepository; + private final ParticipantSearchRepository participantSearchRepository; + private final CertificationsSearchRepository certificationsSearchRepository; + private final InventorySearchRepository inventorySearchRepository; + private final CertificationService certificationService; private final MemberService memberService; - private final RoomCertificationService roomCertificationService; private final NotificationService notificationService; private final ClockHolder clockHolder; @@ -76,7 +84,7 @@ public RoomDetailsResponse getRoomDetails(Long memberId, Long roomId, LocalDate certificationsSearchRepository.findSortedDailyMemberCertifications(roomId, date); List routineResponses = getRoutineResponses(roomId); List todayCertificateRankResponses = getTodayCertificateRankResponses(memberId, - roomId, dailyMemberCertifications, date); + roomId, dailyMemberCertifications, date, room.getRoomType()); List certifiedDates = getCertifiedDatesBeforeWeek(roomId); double completePercentage = calculateCompletePercentage(dailyMemberCertifications.size(), room.getCurrentUserCount()); @@ -86,15 +94,14 @@ public RoomDetailsResponse getRoomDetails(Long memberId, Long roomId, LocalDate } public MyRoomsResponse getMyRooms(Long memberId) { - LocalDate today = clockHolder.times().toLocalDate(); + LocalDate today = clockHolder.date(); List myRoomResponses = new ArrayList<>(); List participants = participantSearchRepository.findNotDeletedParticipantsByMemberId(memberId); for (Participant participant : participants) { Room room = participant.getRoom(); - boolean isMemberCertified = roomCertificationService.existsMemberCertification(memberId, room.getId(), - today); - boolean isRoomCertified = roomCertificationService.existsRoomCertification(room.getId(), today); + boolean isMemberCertified = certificationService.existsMemberCertification(memberId, room.getId(), today); + boolean isRoomCertified = certificationService.existsRoomCertification(room.getId(), today); myRoomResponses.add(RoomMapper.toMyRoomResponse(room, isMemberCertified, isRoomCertified)); } @@ -104,24 +111,22 @@ public MyRoomsResponse getMyRooms(Long memberId) { public RoomsHistoryResponse getJoinHistory(Long memberId) { List participants = participantSearchRepository.findAllParticipantsByMemberId(memberId); - List roomHistoryResponses = new ArrayList<>(); + List roomHistoryResponses = participants.stream() + .map(participant -> { + if (participant.getRoom() == null) { + return RoomMapper.toRoomHistoryResponse(null, participant.getDeletedRoomTitle(), participant); + } - for (Participant participant : participants) { - if (participant.getRoom() == null) { - roomHistoryResponses.add(RoomMapper.toRoomHistoryResponse(null, - participant.getDeletedRoomTitle(), participant)); - - continue; - } + Room room = participant.getRoom(); - roomHistoryResponses.add(RoomMapper.toRoomHistoryResponse(participant.getRoom().getId(), - participant.getRoom().getTitle(), participant)); - } + return RoomMapper.toRoomHistoryResponse(room.getId(), room.getTitle(), participant); + }) + .toList(); return RoomMapper.toRoomsHistoryResponse(roomHistoryResponses); } - public ManageRoomResponse getRoomDetailsBeforeModification(Long memberId, Long roomId) { + public ManageRoomResponse getRoomForModification(Long memberId, Long roomId) { Participant participant = participantSearchRepository.findOne(memberId, roomId) .orElseThrow(() -> new NotFoundException(PARTICIPANT_NOT_FOUND)); @@ -132,13 +137,14 @@ public ManageRoomResponse getRoomDetailsBeforeModification(Long memberId, Long r Room room = participant.getRoom(); List routineResponses = getRoutineResponses(roomId); List participants = participantSearchRepository.findParticipantsByRoomId(roomId); - List memberIds = participants.stream().map(Participant::getMemberId).toList(); + List memberIds = participants.stream() + .map(Participant::getMemberId) + .toList(); List members = memberService.getRoomMembers(memberIds); List participantResponses = new ArrayList<>(); for (Member member : members) { - int contributionPoint = calculateContributionPoint(member.getId(), participants, - clockHolder.times().toLocalDate()); + int contributionPoint = calculateContributionPoint(member.getId(), participants, clockHolder.date()); participantResponses.add(ParticipantMapper.toParticipantResponse(member, contributionPoint)); } @@ -146,16 +152,17 @@ public ManageRoomResponse getRoomDetailsBeforeModification(Long memberId, Long r return RoomMapper.toManageRoomResponse(room, routineResponses, participantResponses); } - public SearchAllRoomsResponse searchAllRooms(@Nullable RoomType roomType, @Nullable Long roomId) { - List searchAllRoomResponses = new ArrayList<>(); + public GetAllRoomsResponse getAllRooms(@Nullable RoomType roomType, @Nullable Long roomId) { + List getAllRoomResponse = new ArrayList<>(); List rooms = new ArrayList<>(roomSearchRepository.findAllWithNoOffset(roomType, roomId)); - boolean hasNext = isHasNext(searchAllRoomResponses, rooms); + boolean hasNext = isHasNext(getAllRoomResponse, rooms); - return RoomMapper.toSearchAllRoomsResponse(hasNext, searchAllRoomResponses); + return RoomMapper.toSearchAllRoomsResponse(hasNext, getAllRoomResponse); } - public SearchAllRoomsResponse search(String keyword, @Nullable RoomType roomType, @Nullable Long roomId) { - List searchAllRoomResponses = new ArrayList<>(); + // TODO: full-text search 로 바꾸면서 리팩토링 예정 + public GetAllRoomsResponse searchRooms(String keyword, @Nullable RoomType roomType, @Nullable Long roomId) { + List getAllRoomResponse = new ArrayList<>(); List rooms = new ArrayList<>(); if (roomId == null && roomType == null) { @@ -175,12 +182,12 @@ public SearchAllRoomsResponse search(String keyword, @Nullable RoomType roomType roomRepository.searchByKeywordAndRoomIdAndRoomType(keyword, roomType.name(), roomId)); } - boolean hasNext = isHasNext(searchAllRoomResponses, rooms); + boolean hasNext = isHasNext(getAllRoomResponse, rooms); - return RoomMapper.toSearchAllRoomsResponse(hasNext, searchAllRoomResponses); + return RoomMapper.toSearchAllRoomsResponse(hasNext, getAllRoomResponse); } - private boolean isHasNext(List searchAllRoomResponses, List rooms) { + private boolean isHasNext(List getAllRoomResponse, List rooms) { boolean hasNext = false; if (rooms.size() > ROOM_FIXED_SEARCH_SIZE) { @@ -188,31 +195,32 @@ private boolean isHasNext(List searchAllRoomResponses, Li rooms.remove(ROOM_FIXED_SEARCH_SIZE); } - List roomIds = rooms.stream().map(Room::getId).toList(); - List routines = routineSearchRepository.findAllByRoomIds(roomIds); + List roomIds = rooms.stream() + .map(Room::getId) + .toList(); + List routines = routineRepository.findAllByRoomIdIn(roomIds); for (Room room : rooms) { List filteredRoutines = routines.stream() .filter(routine -> routine.getRoom().getId().equals(room.getId())) .toList(); - + List filteredResponses = RoutineMapper.toRoutineResponses(filteredRoutines); boolean isPassword = !isEmpty(room.getPassword()); - searchAllRoomResponses.add( - RoomMapper.toSearchAllRoomResponse(room, RoutineMapper.toRoutineResponses(filteredRoutines), - isPassword)); + getAllRoomResponse.add(RoomMapper.toSearchAllRoomResponse(room, filteredResponses, isPassword)); } + return hasNext; } private List getRoutineResponses(Long roomId) { - List roomRoutines = routineSearchRepository.findAllByRoomId(roomId); + List roomRoutines = routineRepository.findAllByRoomId(roomId); return RoutineMapper.toRoutineResponses(roomRoutines); } private List getTodayCertificateRankResponses(Long memberId, Long roomId, - List dailyMemberCertifications, LocalDate date) { + List dailyMemberCertifications, LocalDate date, RoomType roomType) { List responses = new ArrayList<>(); List certifications = certificationsSearchRepository.findCertifications(roomId, date); @@ -221,22 +229,27 @@ private List getTodayCertificateRankResponses(Long .map(Participant::getMemberId) .toList()); - List myKnockedNotificationStatusInRoom = notificationService.getMyKnockStatusInRoom( - memberId, roomId, participants); + List knocks = notificationService.getMyKnockStatusInRoom(memberId, roomId, participants); + + List memberIds = members.stream() + .map(Member::getId) + .toList(); + List inventories = inventorySearchRepository.findDefaultInventories(memberIds, roomType.name()); - addCompletedMembers(responses, dailyMemberCertifications, members, certifications, participants, date, - myKnockedNotificationStatusInRoom); - addUncompletedMembers(responses, dailyMemberCertifications, members, participants, date, - myKnockedNotificationStatusInRoom); + responses.addAll(completedMembers(dailyMemberCertifications, members, certifications, participants, date, + knocks, inventories)); + responses.addAll(uncompletedMembers(dailyMemberCertifications, members, participants, date, knocks, + inventories)); return responses; } - private void addCompletedMembers(List responses, + private List completedMembers( List dailyMemberCertifications, List members, - List certifications, List participants, LocalDate date, - List myKnockedNotificationStatusInRoom) { + List certifications, List participants, LocalDate date, List knocks, + List inventories) { + List responses = new ArrayList<>(); int rank = 1; for (DailyMemberCertification certification : dailyMemberCertifications) { @@ -245,24 +258,33 @@ private void addCompletedMembers(List responses, .findAny() .orElseThrow(() -> new NotFoundException(ROOM_DETAILS_ERROR)); - int contributionPoint = calculateContributionPoint(member.getId(), participants, date); - List certificationImageResponses = - CertificationsMapper.toCertificateImageResponses(member.getId(), certifications); + Inventory inventory = inventories.stream() + .filter(i -> i.getMemberId().equals(member.getId())) + .findAny() + .orElseThrow(() -> new NotFoundException(INVENTORY_NOT_FOUND)); - boolean isNotificationSent = myKnockedNotificationStatusInRoom.contains(member.getId()); + String awakeImage = inventory.getItem().getImage(); + String sleepImage = inventory.getItem().getImage(); - TodayCertificateRankResponse response = CertificationsMapper.toTodayCertificateRankResponse( - rank, member, contributionPoint, "https://~awake", "https://~sleep", certificationImageResponses, - isNotificationSent); + int contributionPoint = calculateContributionPoint(member.getId(), participants, date); + CertificationImagesResponse certificationImages = getCertificationImages(member.getId(), certifications); + boolean isNotificationSent = knocks.contains(member.getId()); + + TodayCertificateRankResponse response = CertificationsMapper.toTodayCertificateRankResponse(rank, member, + contributionPoint, awakeImage, sleepImage, certificationImages, isNotificationSent); rank += 1; responses.add(response); } + + return responses; } - private void addUncompletedMembers(List responses, - List dailyMemberCertifications, List members, - List participants, LocalDate date, List myKnockedNotificationStatusInRoom) { + private List uncompletedMembers( + List dailyMemberCertifications, List members, List participants, + LocalDate date, List knocks, List inventories) { + + List responses = new ArrayList<>(); List allMemberIds = participants.stream() .map(Participant::getMemberId) @@ -280,14 +302,35 @@ private void addUncompletedMembers(List responses, .findAny() .orElseThrow(() -> new NotFoundException(ROOM_DETAILS_ERROR)); + Inventory inventory = inventories.stream() + .filter(i -> i.getMemberId().equals(member.getId())) + .findAny() + .orElseThrow(() -> new NotFoundException(INVENTORY_NOT_FOUND)); + + String awakeImage = inventory.getItem().getImage(); + String sleepImage = inventory.getItem().getImage(); + int contributionPoint = calculateContributionPoint(memberId, participants, date); - boolean isNotificationSent = myKnockedNotificationStatusInRoom.contains(member.getId()); + boolean isNotificationSent = knocks.contains(member.getId()); - TodayCertificateRankResponse response = CertificationsMapper.toTodayCertificateRankResponse(500, member, - contributionPoint, "https://~awake", "https://~sleep", null, isNotificationSent); + TodayCertificateRankResponse response = CertificationsMapper.toTodayCertificateRankResponse( + NOT_COMPLETED_RANK, member, contributionPoint, awakeImage, sleepImage, null, + isNotificationSent); responses.add(response); } + + return responses; + } + + private CertificationImagesResponse getCertificationImages(Long memberId, List certifications) { + List certificationImageResponses = certifications.stream() + .filter(certification -> certification.getMemberId().equals(memberId)) + .map(certification -> CertificationsMapper.toCertificateImageResponse(certification.getRoutine().getId(), + certification.getImage())) + .toList(); + + return CertificationsMapper.toCertificateImagesResponse(certificationImageResponses); } private int calculateContributionPoint(Long memberId, List participants, LocalDate date) { @@ -303,9 +346,11 @@ private int calculateContributionPoint(Long memberId, List particip private List getCertifiedDatesBeforeWeek(Long roomId) { List certifications = certificationsSearchRepository.findDailyRoomCertifications( - roomId, clockHolder.times().toLocalDate()); + roomId, clockHolder.date()); - return certifications.stream().map(DailyRoomCertification::getCertifiedAt).toList(); + return certifications.stream() + .map(DailyRoomCertification::getCertifiedAt) + .toList(); } private double calculateCompletePercentage(int certifiedMembersCount, int currentsMembersCount) { diff --git a/src/main/java/com/moabam/api/application/room/mapper/CertificationsMapper.java b/src/main/java/com/moabam/api/application/room/mapper/CertificationsMapper.java index 788c03d2..060e3478 100644 --- a/src/main/java/com/moabam/api/application/room/mapper/CertificationsMapper.java +++ b/src/main/java/com/moabam/api/application/room/mapper/CertificationsMapper.java @@ -1,7 +1,6 @@ package com.moabam.api.application.room.mapper; import java.time.LocalDate; -import java.util.ArrayList; import java.util.List; import com.moabam.api.domain.member.Member; @@ -11,6 +10,7 @@ import com.moabam.api.domain.room.Participant; import com.moabam.api.domain.room.Routine; import com.moabam.api.dto.room.CertificationImageResponse; +import com.moabam.api.dto.room.CertificationImagesResponse; import com.moabam.api.dto.room.TodayCertificateRankResponse; import lombok.AccessLevel; @@ -19,29 +19,22 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class CertificationsMapper { - public static List toCertificateImageResponses(Long memberId, - List certifications) { - - List cftImageResponses = new ArrayList<>(); - List filteredCertifications = certifications.stream() - .filter(certification -> certification.getMemberId().equals(memberId)) - .toList(); - - for (Certification certification : filteredCertifications) { - CertificationImageResponse cftImageResponse = CertificationImageResponse.builder() - .routineId(certification.getRoutine().getId()) - .image(certification.getImage()) - .build(); - - cftImageResponses.add(cftImageResponse); - } + public static CertificationImageResponse toCertificateImageResponse(Long routineId, String image) { + return CertificationImageResponse.builder() + .routineId(routineId) + .image(image) + .build(); + } - return cftImageResponses; + public static CertificationImagesResponse toCertificateImagesResponse(List images) { + return CertificationImagesResponse.builder() + .images(images) + .build(); } public static TodayCertificateRankResponse toTodayCertificateRankResponse(int rank, Member member, int contributionPoint, String awakeImage, String sleepImage, - List certificationImageResponses, boolean isNotificationSent) { + CertificationImagesResponse certificationImagesResponses, boolean isNotificationSent) { return TodayCertificateRankResponse.builder() .rank(rank) @@ -52,13 +45,12 @@ public static TodayCertificateRankResponse toTodayCertificateRankResponse(int ra .contributionPoint(contributionPoint) .awakeImage(awakeImage) .sleepImage(sleepImage) - .certificationImage(certificationImageResponses) + .certificationImage(certificationImagesResponses) .build(); } public static DailyMemberCertification toDailyMemberCertification(Long memberId, Long roomId, Participant participant) { - return DailyMemberCertification.builder() .memberId(memberId) .roomId(roomId) diff --git a/src/main/java/com/moabam/api/application/room/mapper/ParticipantMapper.java b/src/main/java/com/moabam/api/application/room/mapper/ParticipantMapper.java index 0a566e70..3a4ec1da 100644 --- a/src/main/java/com/moabam/api/application/room/mapper/ParticipantMapper.java +++ b/src/main/java/com/moabam/api/application/room/mapper/ParticipantMapper.java @@ -1,6 +1,8 @@ package com.moabam.api.application.room.mapper; import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; import com.moabam.api.dto.room.ParticipantResponse; import lombok.AccessLevel; @@ -9,6 +11,13 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class ParticipantMapper { + public static Participant toParticipant(Room room, Long memberId) { + return Participant.builder() + .room(room) + .memberId(memberId) + .build(); + } + public static ParticipantResponse toParticipantResponse(Member member, int contributionPoint) { return ParticipantResponse.builder() .memberId(member.getId()) diff --git a/src/main/java/com/moabam/api/application/room/mapper/RoomMapper.java b/src/main/java/com/moabam/api/application/room/mapper/RoomMapper.java index 353c2f99..7e7a9d0c 100644 --- a/src/main/java/com/moabam/api/application/room/mapper/RoomMapper.java +++ b/src/main/java/com/moabam/api/application/room/mapper/RoomMapper.java @@ -6,6 +6,8 @@ import com.moabam.api.domain.room.Participant; import com.moabam.api.domain.room.Room; import com.moabam.api.dto.room.CreateRoomRequest; +import com.moabam.api.dto.room.GetAllRoomResponse; +import com.moabam.api.dto.room.GetAllRoomsResponse; import com.moabam.api.dto.room.ManageRoomResponse; import com.moabam.api.dto.room.MyRoomResponse; import com.moabam.api.dto.room.MyRoomsResponse; @@ -14,8 +16,6 @@ import com.moabam.api.dto.room.RoomHistoryResponse; import com.moabam.api.dto.room.RoomsHistoryResponse; import com.moabam.api.dto.room.RoutineResponse; -import com.moabam.api.dto.room.SearchAllRoomResponse; -import com.moabam.api.dto.room.SearchAllRoomsResponse; import com.moabam.api.dto.room.TodayCertificateRankResponse; import lombok.AccessLevel; @@ -107,9 +107,9 @@ public static ManageRoomResponse toManageRoomResponse(Room room, List routineResponses, + public static GetAllRoomResponse toSearchAllRoomResponse(Room room, List routineResponses, boolean isPassword) { - return SearchAllRoomResponse.builder() + return GetAllRoomResponse.builder() .id(room.getId()) .title(room.getTitle()) .image(room.getRoomImage()) @@ -124,11 +124,11 @@ public static SearchAllRoomResponse toSearchAllRoomResponse(Room room, List searchAllRoomResponses) { - return SearchAllRoomsResponse.builder() + public static GetAllRoomsResponse toSearchAllRoomsResponse(boolean hasNext, + List getAllRoomResponse) { + return GetAllRoomsResponse.builder() .hasNext(hasNext) - .rooms(searchAllRoomResponses) + .rooms(getAllRoomResponse) .build(); } } diff --git a/src/main/java/com/moabam/api/domain/coupon/Coupon.java b/src/main/java/com/moabam/api/domain/coupon/Coupon.java index daeda469..e539d1b0 100644 --- a/src/main/java/com/moabam/api/domain/coupon/Coupon.java +++ b/src/main/java/com/moabam/api/domain/coupon/Coupon.java @@ -55,7 +55,7 @@ public class Coupon extends BaseTimeEntity { @Column(name = "stock", nullable = false) private int stock; - @Column(name = "start_at", nullable = false) + @Column(name = "start_at", unique = true, nullable = false) private LocalDate startAt; @Column(name = "open_at", nullable = false) diff --git a/src/main/java/com/moabam/api/domain/coupon/CouponWallet.java b/src/main/java/com/moabam/api/domain/coupon/CouponWallet.java index da0ff343..80994081 100644 --- a/src/main/java/com/moabam/api/domain/coupon/CouponWallet.java +++ b/src/main/java/com/moabam/api/domain/coupon/CouponWallet.java @@ -12,7 +12,6 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.AccessLevel; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -34,9 +33,12 @@ public class CouponWallet extends BaseTimeEntity { @ManyToOne(fetch = FetchType.LAZY) private Coupon coupon; - @Builder private CouponWallet(Long memberId, Coupon coupon) { this.memberId = memberId; this.coupon = coupon; } + + public static CouponWallet create(Long memberId, Coupon coupon) { + return new CouponWallet(memberId, coupon); + } } diff --git a/src/main/java/com/moabam/api/domain/coupon/repository/CouponManageRepository.java b/src/main/java/com/moabam/api/domain/coupon/repository/CouponManageRepository.java new file mode 100644 index 00000000..22f7df8f --- /dev/null +++ b/src/main/java/com/moabam/api/domain/coupon/repository/CouponManageRepository.java @@ -0,0 +1,63 @@ +package com.moabam.api.domain.coupon.repository; + +import static java.util.Objects.*; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Repository; + +import com.moabam.api.infrastructure.redis.ValueRedisRepository; +import com.moabam.api.infrastructure.redis.ZSetRedisRepository; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class CouponManageRepository { + + private static final String STOCK_KEY = "%s_INCR"; + + private final ZSetRedisRepository zSetRedisRepository; + private final ValueRedisRepository valueRedisRepository; + + public void addIfAbsentQueue(String couponName, Long memberId, double registerTime) { + zSetRedisRepository.addIfAbsent(requireNonNull(couponName), requireNonNull(memberId), registerTime); + } + + public Set popMinQueue(String couponName, long count) { + return zSetRedisRepository + .popMin(requireNonNull(couponName), count) + .stream() + .map(tuple -> (Long)tuple.getValue()) + .collect(Collectors.toSet()); + } + + public void deleteQueue(String couponName) { + valueRedisRepository.delete(requireNonNull(couponName)); + } + + public int increaseIssuedStock(String couponName) { + String stockKey = String.format(STOCK_KEY, requireNonNull(couponName)); + + return valueRedisRepository + .increment(requireNonNull(stockKey)) + .intValue(); + } + + public int getIssuedStock(String couponName) { + String stockKey = String.format(STOCK_KEY, requireNonNull(couponName)); + String stockValue = valueRedisRepository.get(requireNonNull(stockKey)); + + if (stockValue == null) { + return 0; + } + + return Integer.parseInt(stockValue); + } + + public void deleteIssuedStock(String couponName) { + String stockKey = String.format(STOCK_KEY, requireNonNull(couponName)); + valueRedisRepository.delete(requireNonNull(stockKey)); + } +} diff --git a/src/main/java/com/moabam/api/domain/coupon/repository/CouponQueueRepository.java b/src/main/java/com/moabam/api/domain/coupon/repository/CouponQueueRepository.java deleted file mode 100644 index a7ab35ef..00000000 --- a/src/main/java/com/moabam/api/domain/coupon/repository/CouponQueueRepository.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.moabam.api.domain.coupon.repository; - -import static java.util.Objects.*; - -import org.springframework.stereotype.Repository; - -import com.moabam.api.infrastructure.redis.ZSetRedisRepository; - -import lombok.RequiredArgsConstructor; - -@Repository -@RequiredArgsConstructor -public class CouponQueueRepository { - - private final ZSetRedisRepository zSetRedisRepository; - - public void addIfAbsent(String couponName, String memberNickname, double score) { - zSetRedisRepository.addIfAbsent(requireNonNull(couponName), requireNonNull(memberNickname), score); - } - - public Long size(String couponName) { - return zSetRedisRepository.size(requireNonNull(couponName)); - } -} diff --git a/src/main/java/com/moabam/api/domain/coupon/repository/CouponRepository.java b/src/main/java/com/moabam/api/domain/coupon/repository/CouponRepository.java index 02760025..38b9dbe1 100644 --- a/src/main/java/com/moabam/api/domain/coupon/repository/CouponRepository.java +++ b/src/main/java/com/moabam/api/domain/coupon/repository/CouponRepository.java @@ -1,5 +1,6 @@ package com.moabam.api.domain.coupon.repository; +import java.time.LocalDate; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -8,7 +9,9 @@ public interface CouponRepository extends JpaRepository { - Optional findByName(String couponName); - boolean existsByName(String name); + + boolean existsByStartAt(LocalDate startAt); + + Optional findByStartAt(LocalDate startAt); } diff --git a/src/main/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepository.java b/src/main/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepository.java index abd93f5c..afcdbf16 100644 --- a/src/main/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepository.java +++ b/src/main/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepository.java @@ -3,11 +3,13 @@ import static com.moabam.api.domain.coupon.QCoupon.*; import static com.moabam.api.domain.coupon.QCouponWallet.*; +import java.util.List; import java.util.Optional; import org.springframework.stereotype.Repository; import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.global.common.util.DynamicQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -18,6 +20,17 @@ public class CouponWalletSearchRepository { private final JPAQueryFactory jpaQueryFactory; + public List findAllByCouponIdAndMemberId(Long couponId, Long memberId) { + return jpaQueryFactory + .selectFrom(couponWallet) + .join(couponWallet.coupon, coupon).fetchJoin() + .where( + DynamicQuery.generateEq(couponId, couponWallet.coupon.id::eq), + DynamicQuery.generateEq(memberId, couponWallet.memberId::eq) + ) + .fetch(); + } + public Optional findByIdAndMemberId(Long id, Long memberId) { return Optional.ofNullable(jpaQueryFactory .selectFrom(couponWallet) 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 4582ed02..0141d2e4 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,25 @@ public List findItems(Long memberId, ItemType type) { .select(item) .fetch(); } + + public List findDefaultSkin(Long memberId) { + return jpaQueryFactory.selectFrom(inventory) + .join(inventory.item) + .on(inventory.item.id.eq(item.id)) + .where( + inventory.memberId.eq(memberId), + inventory.isDefault.isTrue() + ).fetch(); + } + + public List findDefaultInventories(List memberId, String roomType) { + return jpaQueryFactory.selectFrom(inventory) + .join(inventory.item, item).fetchJoin() + .where( + inventory.memberId.in(memberId), + inventory.isDefault.isTrue(), + inventory.item.type.eq(ItemType.valueOf(roomType)) + ) + .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/Member.java b/src/main/java/com/moabam/api/domain/member/Member.java index 668ea534..b013d7fc 100644 --- a/src/main/java/com/moabam/api/domain/member/Member.java +++ b/src/main/java/com/moabam/api/domain/member/Member.java @@ -10,6 +10,7 @@ import org.hibernate.annotations.SQLDelete; import com.moabam.api.domain.bug.Bug; +import com.moabam.api.domain.room.RoomType; import com.moabam.global.common.entity.BaseTimeEntity; import com.moabam.global.common.util.BaseImageUrl; @@ -83,28 +84,30 @@ private Member(Long id, String socialId, Bug bug) { this.id = id; this.socialId = requireNonNull(socialId); this.nickname = createNickName(); - this.profileImage = BaseImageUrl.PROFILE_URL; + this.profileImage = BaseImageUrl.MEMBER_PROFILE_URL; this.bug = requireNonNull(bug); this.role = Role.USER; } - public void enterMorningRoom() { - currentMorningCount++; - } + public void enterRoom(RoomType roomType) { + if (roomType.equals(RoomType.MORNING)) { + this.currentMorningCount++; + return; + } - public void enterNightRoom() { - currentNightCount++; + if (roomType.equals(RoomType.NIGHT)) { + this.currentNightCount++; + } } - public void exitMorningRoom() { - if (currentMorningCount > 0) { - currentMorningCount--; + public void exitRoom(RoomType roomType) { + if (roomType.equals(RoomType.MORNING) && currentMorningCount > 0) { + this.currentMorningCount--; + return; } - } - public void exitNightRoom() { - if (currentNightCount > 0) { - currentNightCount--; + if (roomType.equals(RoomType.NIGHT) && currentNightCount > 0) { + this.currentNightCount--; } } 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/domain/notification/repository/NotificationRepository.java b/src/main/java/com/moabam/api/domain/notification/repository/NotificationRepository.java index aaffa123..30cdd5d3 100644 --- a/src/main/java/com/moabam/api/domain/notification/repository/NotificationRepository.java +++ b/src/main/java/com/moabam/api/domain/notification/repository/NotificationRepository.java @@ -7,7 +7,7 @@ import org.springframework.stereotype.Repository; -import com.moabam.api.infrastructure.redis.StringRedisRepository; +import com.moabam.api.infrastructure.redis.ValueRedisRepository; import lombok.RequiredArgsConstructor; @@ -15,19 +15,22 @@ @RequiredArgsConstructor public class NotificationRepository { + private static final String KNOCK_KEY = "room_%s_member_%s_knocks_%s"; private static final long EXPIRE_KNOCK = 12; - private final StringRedisRepository stringRedisRepository; + private final ValueRedisRepository valueRedisRepository; - public void saveKnock(String key) { - stringRedisRepository.save( - requireNonNull(key), - BLANK, - Duration.ofHours(EXPIRE_KNOCK) - ); + public void saveKnock(Long memberId, Long targetId, Long roomId) { + String knockKey = + String.format(KNOCK_KEY, requireNonNull(roomId), requireNonNull(memberId), requireNonNull(targetId)); + + valueRedisRepository.save(knockKey, BLANK, Duration.ofHours(EXPIRE_KNOCK)); } - public boolean existsKnockByKey(String key) { - return stringRedisRepository.hasKey(requireNonNull(key)); + public boolean existsKnockByKey(Long memberId, Long targetId, Long roomId) { + String knockKey = + String.format(KNOCK_KEY, requireNonNull(roomId), requireNonNull(memberId), requireNonNull(targetId)); + + return valueRedisRepository.hasKey(requireNonNull(knockKey)); } } diff --git a/src/main/java/com/moabam/api/domain/room/repository/RoutineRepository.java b/src/main/java/com/moabam/api/domain/room/repository/RoutineRepository.java index d4f0e1b9..add3c3be 100644 --- a/src/main/java/com/moabam/api/domain/room/repository/RoutineRepository.java +++ b/src/main/java/com/moabam/api/domain/room/repository/RoutineRepository.java @@ -1,9 +1,14 @@ package com.moabam.api.domain.room.repository; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; import com.moabam.api.domain.room.Routine; public interface RoutineRepository extends JpaRepository { + List findAllByRoomId(Long roomId); + + List findAllByRoomIdIn(List roomIds); } diff --git a/src/main/java/com/moabam/api/domain/room/repository/RoutineSearchRepository.java b/src/main/java/com/moabam/api/domain/room/repository/RoutineSearchRepository.java deleted file mode 100644 index 087d22b6..00000000 --- a/src/main/java/com/moabam/api/domain/room/repository/RoutineSearchRepository.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.moabam.api.domain.room.repository; - -import static com.moabam.api.domain.room.QRoutine.*; - -import java.util.List; - -import org.springframework.stereotype.Repository; - -import com.moabam.api.domain.room.Routine; -import com.querydsl.jpa.impl.JPAQueryFactory; - -import lombok.RequiredArgsConstructor; - -@Repository -@RequiredArgsConstructor -public class RoutineSearchRepository { - - private final JPAQueryFactory jpaQueryFactory; - - public List findAllByRoomId(Long roomId) { - return jpaQueryFactory - .selectFrom(routine) - .where( - routine.room.id.eq(roomId) - ) - .fetch(); - } - - public List findAllByRoomIds(List roomIds) { - return jpaQueryFactory - .selectFrom(routine) - .where( - routine.room.id.in(roomIds) - ) - .fetch(); - } -} diff --git a/src/main/java/com/moabam/api/dto/coupon/MyCouponResponse.java b/src/main/java/com/moabam/api/dto/coupon/MyCouponResponse.java new file mode 100644 index 00000000..a0201ebb --- /dev/null +++ b/src/main/java/com/moabam/api/dto/coupon/MyCouponResponse.java @@ -0,0 +1,16 @@ +package com.moabam.api.dto.coupon; + +import com.moabam.api.domain.coupon.CouponType; + +import lombok.Builder; + +@Builder +public record MyCouponResponse( + Long id, + String name, + String description, + int point, + CouponType type +) { + +} 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/dto/room/CertificationImagesResponse.java b/src/main/java/com/moabam/api/dto/room/CertificationImagesResponse.java new file mode 100644 index 00000000..7de93159 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/CertificationImagesResponse.java @@ -0,0 +1,12 @@ +package com.moabam.api.dto.room; + +import java.util.List; + +import lombok.Builder; + +@Builder +public record CertificationImagesResponse( + List images +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/SearchAllRoomResponse.java b/src/main/java/com/moabam/api/dto/room/GetAllRoomResponse.java similarity index 90% rename from src/main/java/com/moabam/api/dto/room/SearchAllRoomResponse.java rename to src/main/java/com/moabam/api/dto/room/GetAllRoomResponse.java index cdb35ab4..bff4f0ba 100644 --- a/src/main/java/com/moabam/api/dto/room/SearchAllRoomResponse.java +++ b/src/main/java/com/moabam/api/dto/room/GetAllRoomResponse.java @@ -7,7 +7,7 @@ import lombok.Builder; @Builder -public record SearchAllRoomResponse( +public record GetAllRoomResponse( Long id, String title, String image, diff --git a/src/main/java/com/moabam/api/dto/room/SearchAllRoomsResponse.java b/src/main/java/com/moabam/api/dto/room/GetAllRoomsResponse.java similarity index 61% rename from src/main/java/com/moabam/api/dto/room/SearchAllRoomsResponse.java rename to src/main/java/com/moabam/api/dto/room/GetAllRoomsResponse.java index 1d2eb960..bb648e72 100644 --- a/src/main/java/com/moabam/api/dto/room/SearchAllRoomsResponse.java +++ b/src/main/java/com/moabam/api/dto/room/GetAllRoomsResponse.java @@ -5,9 +5,9 @@ import lombok.Builder; @Builder -public record SearchAllRoomsResponse( +public record GetAllRoomsResponse( boolean hasNext, - List rooms + List rooms ) { } diff --git a/src/main/java/com/moabam/api/dto/room/TodayCertificateRankResponse.java b/src/main/java/com/moabam/api/dto/room/TodayCertificateRankResponse.java index 414be53a..c35829d4 100644 --- a/src/main/java/com/moabam/api/dto/room/TodayCertificateRankResponse.java +++ b/src/main/java/com/moabam/api/dto/room/TodayCertificateRankResponse.java @@ -1,7 +1,5 @@ package com.moabam.api.dto.room; -import java.util.List; - import lombok.Builder; @Builder @@ -14,7 +12,7 @@ public record TodayCertificateRankResponse( int contributionPoint, String awakeImage, String sleepImage, - List certificationImage + CertificationImagesResponse certificationImage ) { } diff --git a/src/main/java/com/moabam/api/infrastructure/fcm/FcmRepository.java b/src/main/java/com/moabam/api/infrastructure/fcm/FcmRepository.java index eaa5c55b..39b97666 100644 --- a/src/main/java/com/moabam/api/infrastructure/fcm/FcmRepository.java +++ b/src/main/java/com/moabam/api/infrastructure/fcm/FcmRepository.java @@ -6,7 +6,7 @@ import org.springframework.stereotype.Repository; -import com.moabam.api.infrastructure.redis.StringRedisRepository; +import com.moabam.api.infrastructure.redis.ValueRedisRepository; import lombok.RequiredArgsConstructor; @@ -16,10 +16,10 @@ public class FcmRepository { private static final long EXPIRE_FCM_TOKEN = 60; - private final StringRedisRepository stringRedisRepository; + private final ValueRedisRepository valueRedisRepository; public void saveToken(Long memberId, String fcmToken) { - stringRedisRepository.save( + valueRedisRepository.save( String.valueOf(requireNonNull(memberId)), requireNonNull(fcmToken), Duration.ofDays(EXPIRE_FCM_TOKEN) @@ -27,14 +27,14 @@ public void saveToken(Long memberId, String fcmToken) { } public void deleteTokenByMemberId(Long memberId) { - stringRedisRepository.delete(String.valueOf(requireNonNull(memberId))); + valueRedisRepository.delete(String.valueOf(requireNonNull(memberId))); } public String findTokenByMemberId(Long memberId) { - return stringRedisRepository.get(String.valueOf(requireNonNull(memberId))); + return valueRedisRepository.get(String.valueOf(requireNonNull(memberId))); } public boolean existsTokenByMemberId(Long memberId) { - return stringRedisRepository.hasKey(String.valueOf(requireNonNull(memberId))); + return valueRedisRepository.hasKey(String.valueOf(requireNonNull(memberId))); } } diff --git a/src/main/java/com/moabam/api/infrastructure/redis/StringRedisRepository.java b/src/main/java/com/moabam/api/infrastructure/redis/ValueRedisRepository.java similarity index 83% rename from src/main/java/com/moabam/api/infrastructure/redis/StringRedisRepository.java rename to src/main/java/com/moabam/api/infrastructure/redis/ValueRedisRepository.java index ce1f067b..9f456e9a 100644 --- a/src/main/java/com/moabam/api/infrastructure/redis/StringRedisRepository.java +++ b/src/main/java/com/moabam/api/infrastructure/redis/ValueRedisRepository.java @@ -9,7 +9,7 @@ @Repository @RequiredArgsConstructor -public class StringRedisRepository { +public class ValueRedisRepository { private final RedisTemplate redisTemplate; @@ -19,8 +19,10 @@ public void save(String key, String value, Duration timeout) { .set(key, value, timeout); } - public void delete(String key) { - redisTemplate.delete(key); + public Long increment(String key) { + return redisTemplate + .opsForValue() + .increment(key); } public String get(String key) { @@ -32,4 +34,8 @@ public String get(String key) { public Boolean hasKey(String key) { return redisTemplate.hasKey(key); } + + public void delete(String key) { + redisTemplate.delete(key); + } } diff --git a/src/main/java/com/moabam/api/infrastructure/redis/ZSetRedisRepository.java b/src/main/java/com/moabam/api/infrastructure/redis/ZSetRedisRepository.java index e02a3c4e..9ac7c607 100644 --- a/src/main/java/com/moabam/api/infrastructure/redis/ZSetRedisRepository.java +++ b/src/main/java/com/moabam/api/infrastructure/redis/ZSetRedisRepository.java @@ -1,6 +1,11 @@ package com.moabam.api.infrastructure.redis; +import static java.util.Objects.*; + +import java.util.Set; + import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations.TypedTuple; import org.springframework.stereotype.Repository; import lombok.RequiredArgsConstructor; @@ -11,25 +16,17 @@ public class ZSetRedisRepository { private final RedisTemplate redisTemplate; - public void addIfAbsent(String key, String value, double score) { + public void addIfAbsent(String key, Object value, double score) { if (redisTemplate.opsForZSet().score(key, value) == null) { redisTemplate .opsForZSet() - .add(key, value, score); + .add(requireNonNull(key), requireNonNull(value), score); } } - public Long size(String key) { + public Set> popMin(String key, long count) { return redisTemplate .opsForZSet() - .size(key); - } - - public Boolean hasKey(String key) { - return redisTemplate.hasKey(key); - } - - public void delete(String key) { - redisTemplate.delete(key); + .popMin(key, count); } } diff --git a/src/main/java/com/moabam/api/presentation/CouponController.java b/src/main/java/com/moabam/api/presentation/CouponController.java index 41d817ae..e61b5c6b 100644 --- a/src/main/java/com/moabam/api/presentation/CouponController.java +++ b/src/main/java/com/moabam/api/presentation/CouponController.java @@ -12,11 +12,12 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import com.moabam.api.application.coupon.CouponQueueService; +import com.moabam.api.application.coupon.CouponManageService; import com.moabam.api.application.coupon.CouponService; import com.moabam.api.dto.coupon.CouponResponse; import com.moabam.api.dto.coupon.CouponStatusRequest; import com.moabam.api.dto.coupon.CreateCouponRequest; +import com.moabam.api.dto.coupon.MyCouponResponse; import com.moabam.global.auth.annotation.Auth; import com.moabam.global.auth.model.AuthMember; @@ -28,7 +29,7 @@ public class CouponController { private final CouponService couponService; - private final CouponQueueService couponQueueService; + private final CouponManageService couponManageService; @PostMapping("/admins/coupons") @ResponseStatus(HttpStatus.CREATED) @@ -55,7 +56,13 @@ public List getAllByStatus(@Valid @RequestBody CouponStatusReque } @PostMapping("/coupons") - public void registerCouponQueue(@Auth AuthMember authMember, @RequestParam("couponName") String couponName) { - couponQueueService.register(authMember, couponName); + public void registerQueue(@Auth AuthMember authMember, @RequestParam("couponName") String couponName) { + couponManageService.register(authMember, couponName); + } + + @GetMapping({"/my-coupons", "/my-coupons/{couponId}"}) + public List getWallet(@Auth AuthMember authMember, + @PathVariable(value = "couponId", required = false) Long couponId) { + return couponService.getWallet(couponId, authMember); } } 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/api/presentation/RoomController.java b/src/main/java/com/moabam/api/presentation/RoomController.java index 9229e9df..c3d720fd 100644 --- a/src/main/java/com/moabam/api/presentation/RoomController.java +++ b/src/main/java/com/moabam/api/presentation/RoomController.java @@ -17,18 +17,20 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import com.moabam.api.application.room.RoomCertificationService; -import com.moabam.api.application.room.RoomSearchService; +import com.moabam.api.application.image.ImageService; +import com.moabam.api.application.room.CertificationService; import com.moabam.api.application.room.RoomService; +import com.moabam.api.application.room.SearchService; +import com.moabam.api.domain.image.ImageType; import com.moabam.api.domain.room.RoomType; import com.moabam.api.dto.room.CreateRoomRequest; import com.moabam.api.dto.room.EnterRoomRequest; +import com.moabam.api.dto.room.GetAllRoomsResponse; import com.moabam.api.dto.room.ManageRoomResponse; import com.moabam.api.dto.room.ModifyRoomRequest; import com.moabam.api.dto.room.MyRoomsResponse; import com.moabam.api.dto.room.RoomDetailsResponse; import com.moabam.api.dto.room.RoomsHistoryResponse; -import com.moabam.api.dto.room.SearchAllRoomsResponse; import com.moabam.global.auth.annotation.Auth; import com.moabam.global.auth.model.AuthMember; @@ -41,30 +43,33 @@ public class RoomController { private final RoomService roomService; - private final RoomSearchService roomSearchService; - private final RoomCertificationService roomCertificationService; + private final SearchService searchService; + private final CertificationService certificationService; + private final ImageService imageService; @PostMapping @ResponseStatus(HttpStatus.CREATED) - public Long createRoom(@Auth AuthMember authMember, - @Valid @RequestBody CreateRoomRequest createRoomRequest) { - + public Long createRoom(@Auth AuthMember authMember, @Valid @RequestBody CreateRoomRequest createRoomRequest) { return roomService.createRoom(authMember.id(), authMember.nickname(), createRoomRequest); } - @GetMapping("/{roomId}") + @GetMapping @ResponseStatus(HttpStatus.OK) - public ManageRoomResponse getRoomDetailsBeforeModification(@Auth AuthMember authMember, - @PathVariable("roomId") Long roomId) { + public GetAllRoomsResponse getAllRooms(@RequestParam(value = "roomType", required = false) RoomType roomType, + @RequestParam(value = "roomId", required = false) Long roomId) { + return searchService.getAllRooms(roomType, roomId); + } - return roomSearchService.getRoomDetailsBeforeModification(authMember.id(), roomId); + @GetMapping("/{roomId}") + @ResponseStatus(HttpStatus.OK) + public ManageRoomResponse getRoomForModification(@Auth AuthMember authMember, @PathVariable("roomId") Long roomId) { + return searchService.getRoomForModification(authMember.id(), roomId); } @PutMapping("/{roomId}") @ResponseStatus(HttpStatus.OK) - public void modifyRoom(@Auth AuthMember authMember, - @Valid @RequestBody ModifyRoomRequest modifyRoomRequest, @PathVariable("roomId") Long roomId) { - + public void modifyRoom(@Auth AuthMember authMember, @Valid @RequestBody ModifyRoomRequest modifyRoomRequest, + @PathVariable("roomId") Long roomId) { roomService.modifyRoom(authMember.id(), roomId, modifyRoomRequest); } @@ -72,7 +77,6 @@ public void modifyRoom(@Auth AuthMember authMember, @ResponseStatus(HttpStatus.OK) public void enterRoom(@Auth AuthMember authMember, @PathVariable("roomId") Long roomId, @Valid @RequestBody EnterRoomRequest enterRoomRequest) { - roomService.enterRoom(authMember.id(), roomId, enterRoomRequest); } @@ -84,62 +88,50 @@ public void exitRoom(@Auth AuthMember authMember, @PathVariable("roomId") Long r @GetMapping("/{roomId}/{date}") @ResponseStatus(HttpStatus.OK) - public RoomDetailsResponse getRoomDetails(@Auth AuthMember authMember, - @PathVariable("roomId") Long roomId, @PathVariable("date") LocalDate date) { - - return roomSearchService.getRoomDetails(authMember.id(), roomId, date); + public RoomDetailsResponse getRoomDetails(@Auth AuthMember authMember, @PathVariable("roomId") Long roomId, + @PathVariable("date") LocalDate date) { + return searchService.getRoomDetails(authMember.id(), roomId, date); } @PostMapping("/{roomId}/certification") @ResponseStatus(HttpStatus.CREATED) public void certifyRoom(@Auth AuthMember authMember, @PathVariable("roomId") Long roomId, @RequestPart List multipartFiles) { - - roomCertificationService.certifyRoom(authMember.id(), roomId, multipartFiles); + List imageUrls = imageService.uploadImages(multipartFiles, ImageType.CERTIFICATION); + certificationService.certifyRoom(authMember.id(), roomId, imageUrls); } @PutMapping("/{roomId}/members/{memberId}/mandate") @ResponseStatus(HttpStatus.OK) - public void mandateManager(@Auth AuthMember authMember, - @PathVariable("roomId") Long roomId, @PathVariable("memberId") Long memberId) { - - roomService.mandateRoomManager(authMember.id(), roomId, memberId); + public void mandateManager(@Auth AuthMember authMember, @PathVariable("roomId") Long roomId, + @PathVariable("memberId") Long memberId) { + roomService.mandateManager(authMember.id(), roomId, memberId); } @DeleteMapping("/{roomId}/members/{memberId}") @ResponseStatus(HttpStatus.OK) - public void deportParticipant(@Auth AuthMember authMember, - @PathVariable("roomId") Long roomId, @PathVariable("memberId") Long memberId) { - + public void deportParticipant(@Auth AuthMember authMember, @PathVariable("roomId") Long roomId, + @PathVariable("memberId") Long memberId) { roomService.deportParticipant(authMember.id(), roomId, memberId); } @GetMapping("/my-join") @ResponseStatus(HttpStatus.OK) public MyRoomsResponse getMyRooms(@Auth AuthMember authMember) { - return roomSearchService.getMyRooms(authMember.id()); + return searchService.getMyRooms(authMember.id()); } @GetMapping("/join-history") @ResponseStatus(HttpStatus.OK) public RoomsHistoryResponse getJoinHistory(@Auth AuthMember authMember) { - return roomSearchService.getJoinHistory(authMember.id()); - } - - @GetMapping - @ResponseStatus(HttpStatus.OK) - public SearchAllRoomsResponse searchAllRooms(@RequestParam(value = "roomType", required = false) RoomType roomType, - @RequestParam(value = "roomId", required = false) Long roomId) { - - return roomSearchService.searchAllRooms(roomType, roomId); + return searchService.getJoinHistory(authMember.id()); } @GetMapping("/search") @ResponseStatus(HttpStatus.OK) - public SearchAllRoomsResponse search(@RequestParam(value = "keyword") String keyword, + public GetAllRoomsResponse searchRooms(@RequestParam(value = "keyword") String keyword, @RequestParam(value = "roomType", required = false) RoomType roomType, @RequestParam(value = "roomId", required = false) Long roomId) { - - return roomSearchService.search(keyword, roomType, roomId); + return searchService.searchRooms(keyword, roomType, roomId); } } diff --git a/src/main/java/com/moabam/global/auth/filter/CorsFilter.java b/src/main/java/com/moabam/global/auth/filter/CorsFilter.java new file mode 100644 index 00000000..c113f156 --- /dev/null +++ b/src/main/java/com/moabam/global/auth/filter/CorsFilter.java @@ -0,0 +1,73 @@ +package com.moabam.global.auth.filter; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.servlet.HandlerExceptionResolver; + +import com.google.cloud.storage.HttpMethod; +import com.moabam.global.error.exception.UnauthorizedException; +import com.moabam.global.error.model.ErrorMessage; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Order(0) +@Component +@RequiredArgsConstructor +public class CorsFilter extends OncePerRequestFilter { + + private static final String ALLOWED_METHOD_NAMES = "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH"; + private static final String ALLOWED_HEADERS = "Origin, Accept, Access-Control-Request-Method, " + + "Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer"; + + private final HandlerExceptionResolver handlerExceptionResolver; + + @Value("${allow}") + private String allowOrigin; + + @Override + protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, + FilterChain filterChain) throws ServletException, IOException { + + try { + if (!secureMatch(httpServletRequest, allowOrigin)) { + throw new UnauthorizedException(ErrorMessage.INVALID_REQUEST_URL); + } + } catch (UnauthorizedException unauthorizedException) { + log.error("{}, {}", httpServletRequest.getHeader("referer"), allowOrigin); + handlerExceptionResolver.resolveException(httpServletRequest, httpServletResponse, null, + unauthorizedException); + + return; + } + + httpServletResponse.setHeader("Access-Control-Allow-Origin", allowOrigin); + httpServletResponse.setHeader("Access-Control-Allow-Methods", ALLOWED_METHOD_NAMES); + httpServletResponse.setHeader("Access-Control-Allow-Headers", ALLOWED_HEADERS); + httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true"); + httpServletResponse.setHeader("Access-Control-Max-Age", "3600"); + + if (isOption(httpServletRequest.getMethod())) { + httpServletRequest.setAttribute("isPermit", true); + } + + filterChain.doFilter(httpServletRequest, httpServletResponse); + } + + public boolean secureMatch(HttpServletRequest request, String origin) { + return request.getHeader("referer").contains(origin); + } + + public boolean isOption(String method) { + return HttpMethod.OPTIONS.name().equals(method); + } +} diff --git a/src/main/java/com/moabam/global/common/util/BaseImageUrl.java b/src/main/java/com/moabam/global/common/util/BaseImageUrl.java index d13f36ff..99544648 100644 --- a/src/main/java/com/moabam/global/common/util/BaseImageUrl.java +++ b/src/main/java/com/moabam/global/common/util/BaseImageUrl.java @@ -6,5 +6,11 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class BaseImageUrl { - public static final String PROFILE_URL = "/profile/baseUrl"; + public static final String DEFAULT_SKIN_URL = ""; + public static final String DEFAULT_MORNING_AWAKE_SKIN_URL = ""; + public static final String DEFAULT_MORNING_SLEEP_SKIN_URL = ""; + public static final String DEFAULT_NIGHT_AWAKE_SKIN_URL = ""; + public static final String DEFAULT_NIGHT_SLEEP_SKIN_URL = ""; + + public static final String MEMBER_PROFILE_URL = "/profile/baseUrl"; } diff --git a/src/main/java/com/moabam/global/common/util/ClockHolder.java b/src/main/java/com/moabam/global/common/util/ClockHolder.java index 414ce25c..1ba7a0c5 100644 --- a/src/main/java/com/moabam/global/common/util/ClockHolder.java +++ b/src/main/java/com/moabam/global/common/util/ClockHolder.java @@ -1,8 +1,11 @@ package com.moabam.global.common.util; +import java.time.LocalDate; import java.time.LocalDateTime; public interface ClockHolder { LocalDateTime times(); + + LocalDate date(); } diff --git a/src/main/java/com/moabam/global/common/util/DynamicQuery.java b/src/main/java/com/moabam/global/common/util/DynamicQuery.java index c3ea6069..47468ee9 100644 --- a/src/main/java/com/moabam/global/common/util/DynamicQuery.java +++ b/src/main/java/com/moabam/global/common/util/DynamicQuery.java @@ -1,12 +1,8 @@ package com.moabam.global.common.util; -import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.function.Function; -import org.springframework.util.CollectionUtils; - import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.SimpleExpression; @@ -35,16 +31,4 @@ public static BooleanExpression generateIsNull(Bool return field.isNotNull(); } - - public static BooleanExpression filterCondition(T condition, Function function) { - T tempCondition = condition; - - if (tempCondition instanceof List c && CollectionUtils.isEmpty(c)) { - tempCondition = null; - } - - return Optional.ofNullable(tempCondition) - .map(function) - .orElse(null); - } } 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..ec45d2ec 100644 --- a/src/main/java/com/moabam/global/common/util/GlobalConstant.java +++ b/src/main/java/com/moabam/global/common/util/GlobalConstant.java @@ -14,7 +14,9 @@ public class GlobalConstant { public static final String SPACE = " "; public static final int ONE_HOUR = 1; public static final int HOURS_IN_A_DAY = 24; + public static final int NOT_COMPLETED_RANK = 500; 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/common/util/SystemClockHolder.java b/src/main/java/com/moabam/global/common/util/SystemClockHolder.java index 8662d0da..79396d91 100644 --- a/src/main/java/com/moabam/global/common/util/SystemClockHolder.java +++ b/src/main/java/com/moabam/global/common/util/SystemClockHolder.java @@ -1,5 +1,6 @@ package com.moabam.global.common.util; +import java.time.LocalDate; import java.time.LocalDateTime; import org.springframework.stereotype.Component; @@ -11,4 +12,9 @@ public class SystemClockHolder implements ClockHolder { public LocalDateTime times() { return LocalDateTime.now(); } + + @Override + public LocalDate date() { + return LocalDate.now(); + } } 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/config/WebConfig.java b/src/main/java/com/moabam/global/config/WebConfig.java index b72e98af..387714cb 100644 --- a/src/main/java/com/moabam/global/config/WebConfig.java +++ b/src/main/java/com/moabam/global/config/WebConfig.java @@ -2,13 +2,11 @@ import java.util.List; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import com.moabam.api.application.auth.mapper.PathMapper; @@ -19,22 +17,6 @@ @EnableScheduling public class WebConfig implements WebMvcConfigurer { - private static final String ALLOWED_METHOD_NAMES = "GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,PATCH"; - private static final String ALLOW_ORIGIN_PATTERN = "[a-z]+\\.moabam.com"; - - @Value("${allow}") - private String allowLocalHost; - - @Override - public void addCorsMappings(final CorsRegistry registry) { - registry.addMapping("/**") - .allowedOriginPatterns(ALLOW_ORIGIN_PATTERN, allowLocalHost) - .allowedMethods(ALLOWED_METHOD_NAMES.split(",")) - .allowedHeaders("*") - .allowCredentials(true) - .maxAge(3600); - } - @Override public void addArgumentResolvers(List resolvers) { resolvers.add(handlerMethodArgumentResolver()); 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..0fc50530 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("존재하지 않는 아이템입니다."), @@ -68,6 +70,7 @@ public enum ErrorMessage { INVALID_COUPON_OPEN_AT_PERIOD("쿠폰 정보 오픈 날짜는 시작 날짜보다 이전이여야 합니다."), INVALID_COUPON_PERIOD("쿠폰 발급 가능 기간이 아닙니다."), CONFLICT_COUPON_NAME("쿠폰의 이름이 중복되었습니다."), + CONFLICT_COUPON_START_AT("쿠폰 발급 가능 날짜가 중복되었습니다."), NOT_FOUND_COUPON_TYPE("존재하지 않는 쿠폰 종류입니다."), NOT_FOUND_COUPON("존재하지 않는 쿠폰입니다."), NOT_FOUND_COUPON_WALLET("보유하지 않은 쿠폰입니다."), diff --git a/src/main/resources/static/docs/coupon.html b/src/main/resources/static/docs/coupon.html index ec72ba0b..4b9cc040 100644 --- a/src/main/resources/static/docs/coupon.html +++ b/src/main/resources/static/docs/coupon.html @@ -1,2135 +1,464 @@ - - - - - 쿠폰(Coupon) - - + + + + +쿠폰(Coupon) + +
-
-

쿠폰(Coupon)

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

쿠폰 생성

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

요청

-
-
+
+

쿠폰(Coupon)

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

쿠폰 생성

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

요청

+
+
POST /admins/coupons HTTP/1.1
 Content-Type: application/json;charset=UTF-8
 Content-Length: 175
@@ -2144,71 +473,77 @@ 

요청

"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와 일치하는 쿠폰을 삭제합니다.
-
-
-

요청

-
-
-
DELETE /admins/coupons/1 HTTP/1.1
+Access-Control-Allow-Origin:
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
+
+
+
+
+
+

쿠폰 삭제

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

요청

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

응답

-
-
+
+
+

응답

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

특정 쿠폰 조회

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

요청

-
-
-
GET /coupons/26 HTTP/1.1
+Access-Control-Allow-Origin:
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
+
+
+
+
+
+

특정 쿠폰 조회

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

요청

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

응답

-
-
+
+
+

응답

+
+
HTTP/1.1 200 OK
-Vary: Origin
-Vary: Access-Control-Request-Method
-Vary: Access-Control-Request-Headers
+Access-Control-Allow-Origin:
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
 Content-Type: application/json
 Content-Length: 205
 
 {
-  "id" : 26,
+  "id" : 22,
   "adminName" : "1admin",
   "name" : "couponName",
   "description" : "",
@@ -2218,22 +553,22 @@ 

응답

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

상태에 따른 쿠폰들을 조회

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

요청

-
-
+
+
+
+
+
+
+

상태에 따른 쿠폰들을 조회

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

요청

+
+
POST /coupons/search HTTP/1.1
 Content-Type: application/json;charset=UTF-8
 Content-Length: 41
@@ -2243,20 +578,22 @@ 

요청

"opened" : false, "ended" : false }
-
-
-

응답

-
-
+
+
+

응답

+
+
HTTP/1.1 200 OK
-Vary: Origin
-Vary: Access-Control-Request-Method
-Vary: Access-Control-Request-Headers
+Access-Control-Allow-Origin:
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
 Content-Type: application/json
 Content-Length: 206
 
 [ {
-  "id" : 15,
+  "id" : 23,
   "adminName" : "1admin",
   "name" : "coupon1",
   "description" : "",
@@ -2266,73 +603,129 @@ 

응답

"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
-Vary: Access-Control-Request-Headers
+Access-Control-Allow-Origin:
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
 Content-Type: application/json
-Content-Length: 66
+Content-Length: 64
 
 {
   "message" : "쿠폰 발급 가능 기간이 아닙니다."
 }
-
-
-
-
-
-
-

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

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

쿠폰 사용 (진행 중)

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

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

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

요청

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

응답

+
+
+
HTTP/1.1 200 OK
+Access-Control-Allow-Origin:
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
+Content-Type: application/json
+Content-Length: 507
+
+[ {
+  "id" : 17,
+  "name" : "c1",
+  "description" : "",
+  "point" : 10,
+  "type" : "MORNING_COUPON"
+}, {
+  "id" : 18,
+  "name" : "c2",
+  "description" : "",
+  "point" : 10,
+  "type" : "MORNING_COUPON"
+}, {
+  "id" : 19,
+  "name" : "c3",
+  "description" : "",
+  "point" : 10,
+  "type" : "MORNING_COUPON"
+}, {
+  "id" : 20,
+  "name" : "c4",
+  "description" : "",
+  "point" : 10,
+  "type" : "MORNING_COUPON"
+}, {
+  "id" : 21,
+  "name" : "c5",
+  "description" : "",
+  "point" : 10,
+  "type" : "MORNING_COUPON"
+} ]
+
+
+
+
+
+
+

쿠폰 사용 (진행 중)

+
+
+
사용자가 자신의 보관함에 있는 쿠폰들을 사용합니다.
+
+
+
+
+
- + \ 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..62f80fd6 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..d56f528e 100644 --- a/src/main/resources/static/docs/notification.html +++ b/src/main/resources/static/docs/notification.html @@ -1,2193 +1,520 @@ - - - - - 알림(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
-Vary: Access-Control-Request-Headers
-Content-Type: application/json
-Content-Length: 66
-
-{
-  "message" : "해당 유저는 접속 중이 아닙니다."
-}
-
-
-
-
-

FCM TOKEN 저장

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

요청

-
-
+
+
+

응답

+
+
+
HTTP/1.1 200 OK
+Access-Control-Allow-Origin:
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
+
+
+
+
+

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
+Content-Length: 18
 
-fcmToken=
-
-
-

응답

-
-
+fcmToken=FCM-TOKEN +
+
+

응답

+
+
HTTP/1.1 200 OK
-Vary: Origin
-Vary: Access-Control-Request-Method
-Vary: Access-Control-Request-Headers
-
-
-
-
-
+Access-Control-Allow-Origin: +Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH +Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer +Access-Control-Allow-Credentials: true +Access-Control-Max-Age: 3600 +
+
+
+
+
- + \ No newline at end of file diff --git a/src/test/java/com/moabam/api/application/bug/BugServiceTest.java b/src/test/java/com/moabam/api/application/bug/BugServiceTest.java index febdcdba..1cde9be1 100644 --- a/src/test/java/com/moabam/api/application/bug/BugServiceTest.java +++ b/src/test/java/com/moabam/api/application/bug/BugServiceTest.java @@ -103,7 +103,7 @@ void apply_coupon_success() { PurchaseProductRequest request = new PurchaseProductRequest(couponWalletId); given(productRepository.findById(productId)).willReturn(Optional.of(bugProduct())); given(paymentRepository.save(any(Payment.class))).willReturn(payment); - given(couponService.getByWallet(memberId, couponWalletId)).willReturn(discount1000Coupon()); + given(couponService.getByWalletIdAndMemberId(couponWalletId, memberId)).willReturn(discount1000Coupon()); // when PurchaseProductResponse response = bugService.purchaseBugProduct(memberId, productId, request); diff --git a/src/test/java/com/moabam/api/application/coupon/CouponManageServiceTest.java b/src/test/java/com/moabam/api/application/coupon/CouponManageServiceTest.java new file mode 100644 index 00000000..e347aba6 --- /dev/null +++ b/src/test/java/com/moabam/api/application/coupon/CouponManageServiceTest.java @@ -0,0 +1,214 @@ +package com.moabam.api.application.coupon; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +import java.time.LocalDate; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +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.coupon.Coupon; +import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.api.domain.coupon.repository.CouponManageRepository; +import com.moabam.api.domain.coupon.repository.CouponRepository; +import com.moabam.api.domain.coupon.repository.CouponWalletRepository; +import com.moabam.global.auth.model.AuthMember; +import com.moabam.global.auth.model.AuthorizationThreadLocal; +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.CouponFixture; + +@ExtendWith({MockitoExtension.class, FilterProcessExtension.class}) +class CouponManageServiceTest { + + @InjectMocks + CouponManageService couponManageService; + + @Mock + CouponRepository couponRepository; + + @Mock + CouponManageRepository couponManageRepository; + + @Mock + CouponWalletRepository couponWalletRepository; + + @Mock + ClockHolder clockHolder; + + @DisplayName("쿠폰 발행이 성공적으로 된다.") + @Test + void issue_all_success() { + // Given + Coupon coupon = CouponFixture.coupon(1000, 100); + Set membersId = new HashSet<>(Set.of(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L)); + + given(clockHolder.date()).willReturn(LocalDate.now()); + given(couponRepository.findByStartAt(any(LocalDate.class))).willReturn(Optional.of(coupon)); + given(couponManageRepository.getIssuedStock(any(String.class))).willReturn(10); + given(couponManageRepository.popMinQueue(any(String.class), any(long.class))).willReturn(membersId); + given(couponManageRepository.increaseIssuedStock(any(String.class))).willReturn(99); + + // When + couponManageService.issue(); + + // Then + verify(couponWalletRepository, times(10)).save(any(CouponWallet.class)); + } + + @DisplayName("발행 가능한 쿠폰이 없다.") + @Test + void issue_notStartAt() { + // Given + given(clockHolder.date()).willReturn(LocalDate.now()); + given(couponRepository.findByStartAt(any(LocalDate.class))).willReturn(Optional.empty()); + + // When + couponManageService.issue(); + + // Then + verify(couponManageRepository, times(0)).getIssuedStock(any(String.class)); + verify(couponManageRepository, times(0)).popMinQueue(any(String.class), any(long.class)); + verify(couponManageRepository, times(0)).increaseIssuedStock(any(String.class)); + verify(couponWalletRepository, times(0)).save(any(CouponWallet.class)); + } + + @DisplayName("해당 쿠폰은 재고가 마감된 쿠폰이다.") + @Test + void issue_stockEnd() { + // Given + Coupon coupon = CouponFixture.coupon(1000, 100); + + given(clockHolder.date()).willReturn(LocalDate.now()); + given(couponRepository.findByStartAt(any(LocalDate.class))).willReturn(Optional.of(coupon)); + given(couponManageRepository.getIssuedStock(any(String.class))).willReturn(coupon.getStock()); + + // When + couponManageService.issue(); + + // Then + verify(couponManageRepository, times(0)).popMinQueue(any(String.class), any(long.class)); + verify(couponManageRepository, times(0)).increaseIssuedStock(any(String.class)); + verify(couponWalletRepository, times(0)).save(any(CouponWallet.class)); + } + + @DisplayName("대기열에 남은 인원이 모두 발급받지 못한다.") + @Test + void issue_queue_stockENd() { + // Given + Coupon coupon = CouponFixture.coupon(1000, 100); + Set membersId = new HashSet<>(Set.of(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L)); + + given(clockHolder.date()).willReturn(LocalDate.now()); + given(couponRepository.findByStartAt(any(LocalDate.class))).willReturn(Optional.of(coupon)); + given(couponManageRepository.getIssuedStock(any(String.class))).willReturn(10); + given(couponManageRepository.popMinQueue(any(String.class), any(long.class))).willReturn(membersId); + given(couponManageRepository.increaseIssuedStock(any(String.class))).willReturn(101); + + // When + couponManageService.issue(); + + // Then + verify(couponWalletRepository, times(0)).save(any(CouponWallet.class)); + } + + @WithMember + @DisplayName("쿠폰 발급 요청을 성공적으로 큐에 등록한다. - Void") + @Test + void register_success() { + // Given + AuthMember member = AuthorizationThreadLocal.getAuthMember(); + Coupon coupon = CouponFixture.coupon(); + + given(clockHolder.date()).willReturn(LocalDate.now()); + given(couponRepository.findByStartAt(any(LocalDate.class))).willReturn(Optional.of(coupon)); + + // When + couponManageService.register(member, coupon.getName()); + + // Then + verify(couponManageRepository).addIfAbsentQueue(any(String.class), any(Long.class), any(double.class)); + } + + @WithMember + @DisplayName("금일 발급이 가능한 쿠폰이 없다. - BadRequestException") + @Test + void register_StartAt_BadRequestException() { + // Given + AuthMember member = AuthorizationThreadLocal.getAuthMember(); + + given(clockHolder.date()).willReturn(LocalDate.now()); + given(couponRepository.findByStartAt(any(LocalDate.class))).willReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> couponManageService.register(member, "couponName")) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.INVALID_COUPON_PERIOD.getMessage()); + } + + @WithMember + @DisplayName("금일 발급 가능한 쿠폰의 이름과 일치하지 않는다. - BadRequestException") + @Test + void register_Name_BadRequestException() { + // Given + AuthMember member = AuthorizationThreadLocal.getAuthMember(); + Coupon coupon = CouponFixture.coupon(); + + given(clockHolder.date()).willReturn(LocalDate.now()); + given(couponRepository.findByStartAt(any(LocalDate.class))).willReturn(Optional.of(coupon)); + + // When & Then + assertThatThrownBy(() -> couponManageService.register(member, "Coupon Cannot Be Issued Today")) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.INVALID_COUPON_PERIOD.getMessage()); + } + + @DisplayName("쿠폰 대기열과 발행된 재고가 정상적으로 삭제된다.") + @Test + void deleteCouponManage_success() { + // Given + String couponName = "couponName"; + + // When + couponManageService.deleteCouponManage(couponName); + + // Then + verify(couponManageRepository).deleteQueue(couponName); + verify(couponManageRepository).deleteIssuedStock(couponName); + } + + @DisplayName("쿠폰 대기열이 정상적으로 삭제되지 않는다.") + @Test + void deleteCouponManage_Queue_NullPointerException() { + // Given + willThrow(NullPointerException.class).given(couponManageRepository).deleteQueue(any(String.class)); + + // When & Then + assertThatThrownBy(() -> couponManageService.deleteCouponManage("null")) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("쿠폰의 발행된 재고가 정상적으로 삭제되지 않는다.") + @Test + void deleteCouponManage_Stock_NullPointerException() { + // Given + willDoNothing().given(couponManageRepository).deleteQueue(any(String.class)); + willThrow(NullPointerException.class).given(couponManageRepository).deleteIssuedStock(any(String.class)); + + // When & Then + assertThatThrownBy(() -> couponManageService.deleteCouponManage("null")) + .isInstanceOf(NullPointerException.class); + } +} diff --git a/src/test/java/com/moabam/api/application/coupon/CouponQueueServiceTest.java b/src/test/java/com/moabam/api/application/coupon/CouponQueueServiceTest.java deleted file mode 100644 index b6721d68..00000000 --- a/src/test/java/com/moabam/api/application/coupon/CouponQueueServiceTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.moabam.api.application.coupon; - -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.coupon.Coupon; -import com.moabam.api.domain.coupon.repository.CouponQueueRepository; -import com.moabam.global.auth.model.AuthMember; -import com.moabam.global.auth.model.AuthorizationThreadLocal; -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.CouponFixture; - -@ExtendWith({MockitoExtension.class, FilterProcessExtension.class}) -class CouponQueueServiceTest { - - @InjectMocks - private CouponQueueService couponQueueService; - - @Mock - private CouponQueueRepository couponQueueRepository; - - @Mock - private CouponService couponService; - - @WithMember - @DisplayName("쿠폰 발급 요청을 성공적으로 큐에 등록한다. - Void") - @Test - void register() { - // Given - AuthMember member = AuthorizationThreadLocal.getAuthMember(); - Coupon coupon = CouponFixture.coupon(); - - given(couponService.validatePeriod(any(String.class))).willReturn(coupon); - given(couponQueueRepository.size(any(String.class))).willReturn(coupon.getStock() - 1L); - - // When - couponQueueService.register(member, coupon.getName()); - - // Then - verify(couponQueueRepository).addIfAbsent(any(String.class), any(String.class), any(double.class)); - } - - @WithMember - @DisplayName("해당 쿠폰은 발급 가능 기간이 아니다. - BadRequestException") - @Test - void register_BadRequestException() { - // Given - AuthMember member = AuthorizationThreadLocal.getAuthMember(); - - given(couponService.validatePeriod(any(String.class))) - .willThrow(new BadRequestException(ErrorMessage.INVALID_COUPON_PERIOD)); - - // When & Then - assertThatThrownBy(() -> couponQueueService.register(member, "couponName")) - .isInstanceOf(BadRequestException.class) - .hasMessage(ErrorMessage.INVALID_COUPON_PERIOD.getMessage()); - } - - @WithMember - @DisplayName("해당 쿠폰은 마감된 쿠폰이다. - Void") - @Test - void register_End() { - // Given - AuthMember member = AuthorizationThreadLocal.getAuthMember(); - Coupon coupon = CouponFixture.coupon(); - - given(couponService.validatePeriod(any(String.class))).willReturn(coupon); - given(couponQueueRepository.size(any(String.class))).willReturn((long)coupon.getStock()); - - // When & Then - assertThatNoException().isThrownBy(() -> couponQueueService.register(member, coupon.getName())); - } -} diff --git a/src/test/java/com/moabam/api/application/coupon/CouponServiceTest.java b/src/test/java/com/moabam/api/application/coupon/CouponServiceTest.java index 18099a8c..a8e19049 100644 --- a/src/test/java/com/moabam/api/application/coupon/CouponServiceTest.java +++ b/src/test/java/com/moabam/api/application/coupon/CouponServiceTest.java @@ -1,10 +1,10 @@ package com.moabam.api.application.coupon; +import static com.moabam.support.fixture.CouponFixture.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.*; import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -19,12 +19,15 @@ import com.moabam.api.domain.coupon.Coupon; import com.moabam.api.domain.coupon.CouponType; +import com.moabam.api.domain.coupon.CouponWallet; import com.moabam.api.domain.coupon.repository.CouponRepository; import com.moabam.api.domain.coupon.repository.CouponSearchRepository; +import com.moabam.api.domain.coupon.repository.CouponWalletSearchRepository; import com.moabam.api.domain.member.Role; import com.moabam.api.dto.coupon.CouponResponse; import com.moabam.api.dto.coupon.CouponStatusRequest; import com.moabam.api.dto.coupon.CreateCouponRequest; +import com.moabam.api.dto.coupon.MyCouponResponse; import com.moabam.global.auth.model.AuthMember; import com.moabam.global.auth.model.AuthorizationThreadLocal; import com.moabam.global.common.util.ClockHolder; @@ -40,27 +43,33 @@ class CouponServiceTest { @InjectMocks - private CouponService couponService; + CouponService couponService; @Mock - private CouponRepository couponRepository; + CouponManageService couponManageService; @Mock - private CouponSearchRepository couponSearchRepository; + CouponRepository couponRepository; @Mock - private ClockHolder clockHolder; + CouponSearchRepository couponSearchRepository; + + @Mock + CouponWalletSearchRepository couponWalletSearchRepository; + + @Mock + ClockHolder clockHolder; @WithMember(role = Role.ADMIN) @DisplayName("쿠폰을 성공적으로 발행한다. - Void") @Test - void create() { + void create_success() { // Given AuthMember admin = AuthorizationThreadLocal.getAuthMember(); CreateCouponRequest request = CouponFixture.createCouponRequest(); given(couponRepository.existsByName(any(String.class))).willReturn(false); - given(clockHolder.times()).willReturn(LocalDateTime.of(2022, 1, 1, 1, 1)); + given(clockHolder.date()).willReturn(LocalDate.of(2022, 1, 1)); // When couponService.create(admin, request); @@ -92,7 +101,7 @@ void create_Type_NotFoundException() { CreateCouponRequest request = CouponFixture.createCouponRequest("UNKNOWN", 2, 1); given(couponRepository.existsByName(any(String.class))).willReturn(false); - given(clockHolder.times()).willReturn(LocalDateTime.of(2022, 1, 1, 1, 1)); + given(clockHolder.date()).willReturn(LocalDate.of(2022, 1, 1)); // When & Then assertThatThrownBy(() -> couponService.create(admin, request)) @@ -103,7 +112,7 @@ void create_Type_NotFoundException() { @WithMember(role = Role.ADMIN) @DisplayName("중복된 쿠폰명을 발행한다. - ConflictException") @Test - void create_ConflictException() { + void create_Name_ConflictException() { // Given AuthMember admin = AuthorizationThreadLocal.getAuthMember(); CreateCouponRequest request = CouponFixture.createCouponRequest(); @@ -116,6 +125,23 @@ void create_ConflictException() { .hasMessage(ErrorMessage.CONFLICT_COUPON_NAME.getMessage()); } + @WithMember(role = Role.ADMIN) + @DisplayName("중복된 쿠폰 발행 가능 날짜를 발행한다. - ConflictException") + @Test + void create_StartAt_ConflictException() { + // Given + AuthMember admin = AuthorizationThreadLocal.getAuthMember(); + CreateCouponRequest request = CouponFixture.createCouponRequest(); + + given(couponRepository.existsByName(any(String.class))).willReturn(false); + given(couponRepository.existsByStartAt(any(LocalDate.class))).willReturn(true); + + // When & Then + assertThatThrownBy(() -> couponService.create(admin, request)) + .isInstanceOf(ConflictException.class) + .hasMessage(ErrorMessage.CONFLICT_COUPON_START_AT.getMessage()); + } + @WithMember(role = Role.ADMIN) @DisplayName("현재 날짜가 쿠폰 발급 가능 날짜와 같거나 이후이다. - BadRequestException") @Test @@ -124,8 +150,9 @@ void create_StartAt_BadRequestException() { AuthMember admin = AuthorizationThreadLocal.getAuthMember(); CreateCouponRequest request = CouponFixture.createCouponRequest(); - given(clockHolder.times()).willReturn(LocalDateTime.of(2025, 1, 1, 1, 1)); + given(clockHolder.date()).willReturn(LocalDate.of(2025, 1, 1)); given(couponRepository.existsByName(any(String.class))).willReturn(false); + given(couponRepository.existsByStartAt(any(LocalDate.class))).willReturn(false); // When & Then assertThatThrownBy(() -> couponService.create(admin, request)) @@ -143,7 +170,8 @@ void create_OpenAt_BadRequestException() { CreateCouponRequest request = CouponFixture.createCouponRequest(couponType, 1, 1); given(couponRepository.existsByName(any(String.class))).willReturn(false); - given(clockHolder.times()).willReturn(LocalDateTime.of(2022, 1, 1, 1, 1)); + given(couponRepository.existsByStartAt(any(LocalDate.class))).willReturn(false); + given(clockHolder.date()).willReturn(LocalDate.of(2022, 1, 1)); // When & Then assertThatThrownBy(() -> couponService.create(admin, request)) @@ -152,12 +180,13 @@ void create_OpenAt_BadRequestException() { } @WithMember(role = Role.ADMIN) - @DisplayName("쿠폰 아이디와 일치하는 쿠폰을 삭제한다. - Void") + @DisplayName("쿠폰 아이디와 일치하는 쿠폰을 성공적으로 삭제한다. - Void") @Test - void delete() { + void delete_success() { // Given AuthMember admin = AuthorizationThreadLocal.getAuthMember(); Coupon coupon = CouponFixture.coupon(10, 100); + given(couponRepository.findById(any(Long.class))).willReturn(Optional.of(coupon)); // When @@ -165,6 +194,7 @@ void delete() { // Then verify(couponRepository).delete(coupon); + verify(couponManageService).deleteCouponManage(any(String.class)); } @WithMember(role = Role.USER) @@ -186,6 +216,7 @@ void delete_Admin_NotFoundException() { void delete_NotFoundException() { // Given AuthMember admin = AuthorizationThreadLocal.getAuthMember(); + given(couponRepository.findById(any(Long.class))).willReturn(Optional.empty()); // When & Then @@ -194,11 +225,12 @@ void delete_NotFoundException() { .hasMessage(ErrorMessage.NOT_FOUND_COUPON.getMessage()); } - @DisplayName("특정 쿠폰을 조회한다. - CouponResponse") + @DisplayName("특정 쿠폰을 성공적으로 조회한다. - CouponResponse") @Test - void getById() { + void getById_success() { // Given Coupon coupon = CouponFixture.coupon(10, 100); + given(couponRepository.findById(any(Long.class))).willReturn(Optional.of(coupon)); // When @@ -221,14 +253,14 @@ void getById_NotFoundException() { .hasMessage(ErrorMessage.NOT_FOUND_COUPON.getMessage()); } - @DisplayName("모든 쿠폰을 조회한다. - List") + @DisplayName("모든 쿠폰을 성공적으로 조회한다. - List") @MethodSource("com.moabam.support.fixture.CouponFixture#provideCoupons") @ParameterizedTest - void getAllByStatus(List coupons) { + void getAllByStatus_success(List coupons) { // Given CouponStatusRequest request = CouponFixture.couponStatusRequest(false, false); - given(clockHolder.times()).willReturn(LocalDateTime.now()); + given(clockHolder.date()).willReturn(LocalDate.now()); given(couponSearchRepository.findAllByStatus(any(LocalDate.class), any(CouponStatusRequest.class))) .willReturn(coupons); @@ -239,48 +271,38 @@ void getAllByStatus(List coupons) { assertThat(actual).hasSize(coupons.size()); } - @DisplayName("해당 쿠폰은 발급 가능 기간입니다. - Coupon") - @Test - void validatePeriod() { + @WithMember + @DisplayName("나의 쿠폰함을 성공적으로 조회한다.") + @MethodSource("com.moabam.support.fixture.CouponWalletFixture#provideCouponWalletByCouponId1_total5") + @ParameterizedTest + void getWallet_success(List couponWallets) { // Given - LocalDateTime now = LocalDateTime.of(2023, 1, 1, 1, 0); - Coupon coupon = CouponFixture.coupon("couponName", 1, 2); - given(couponRepository.findByName(any(String.class))).willReturn(Optional.of(coupon)); - given(clockHolder.times()).willReturn(now); + AuthMember authMember = AuthorizationThreadLocal.getAuthMember(); + + given(couponWalletSearchRepository.findAllByCouponIdAndMemberId(isNull(), any(Long.class))) + .willReturn(couponWallets); // When - Coupon actual = couponService.validatePeriod(coupon.getName()); + List actual = couponService.getWallet(null, authMember); // Then - assertThat(actual.getName()).isEqualTo(coupon.getName()); + assertThat(actual).hasSize(couponWallets.size()); } - @DisplayName("해당 쿠폰은 발급 가능 기간이 아닙니다. - BadRequestException") + @WithMember + @DisplayName("지갑에서 특정 쿠폰을 성공적으로 조회한다.") @Test - void validatePeriod_BadRequestException() { + void getByWalletIdAndMemberId_success() { // Given - LocalDateTime now = LocalDateTime.of(2022, 1, 1, 1, 0); - Coupon coupon = CouponFixture.coupon("couponName", 1, 2); - given(couponRepository.findByName(any(String.class))).willReturn(Optional.of(coupon)); - given(clockHolder.times()).willReturn(now); + CouponWallet couponWallet = CouponWallet.create(1L, coupon()); - // When & Then - assertThatThrownBy(() -> couponService.validatePeriod("couponName")) - .isInstanceOf(BadRequestException.class) - .hasMessage(ErrorMessage.INVALID_COUPON_PERIOD.getMessage()); - } + given(couponWalletSearchRepository.findByIdAndMemberId(any(Long.class), any(Long.class))) + .willReturn(Optional.of(couponWallet)); - @DisplayName("해당 쿠폰은 존재하지 않습니다. - NotFoundException") - @Test - void validatePeriod_NotFoundException() { - // Given - LocalDateTime now = LocalDateTime.of(2022, 1, 1, 1, 0); - given(couponRepository.findByName(any(String.class))).willReturn(Optional.empty()); - given(clockHolder.times()).willReturn(now); + // When + Coupon actual = couponService.getByWalletIdAndMemberId(1L, 1L); - // When & Then - assertThatThrownBy(() -> couponService.validatePeriod("Not found coupon name")) - .isInstanceOf(NotFoundException.class) - .hasMessage(ErrorMessage.NOT_FOUND_COUPON.getMessage()); + // Then + assertThat(actual.getName()).isEqualTo(couponWallet.getCoupon().getName()); } } 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..c1d9025c 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.findDefaultSkin(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.findDefaultSkin(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.findDefaultSkin(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.findDefaultSkin(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/application/notification/NotificationServiceTest.java b/src/test/java/com/moabam/api/application/notification/NotificationServiceTest.java index 60e7e65f..a85c0fc9 100644 --- a/src/test/java/com/moabam/api/application/notification/NotificationServiceTest.java +++ b/src/test/java/com/moabam/api/application/notification/NotificationServiceTest.java @@ -19,7 +19,6 @@ import com.moabam.api.domain.notification.repository.NotificationRepository; import com.moabam.api.domain.room.Participant; import com.moabam.api.domain.room.repository.ParticipantSearchRepository; -import com.moabam.api.infrastructure.fcm.FcmRepository; import com.moabam.api.infrastructure.fcm.FcmService; import com.moabam.global.auth.model.AuthMember; import com.moabam.global.auth.model.AuthorizationThreadLocal; @@ -34,43 +33,41 @@ class NotificationServiceTest { @InjectMocks - private NotificationService notificationService; + NotificationService notificationService; @Mock - private RoomService roomService; + RoomService roomService; @Mock - private FcmRepository fcmRepository; + FcmService fcmService; @Mock - private FcmService fcmService; + NotificationRepository notificationRepository; @Mock - private NotificationRepository notificationRepository; + ParticipantSearchRepository participantSearchRepository; @Mock - private ParticipantSearchRepository participantSearchRepository; - - @Mock - private ClockHolder clockHolder; + ClockHolder clockHolder; @WithMember - @DisplayName("성공적으로 상대에게 콕 알림을 보낸다. - Void") + @DisplayName("상대에게 콕 알림을 성공적으로 보낸다. - Void") @Test - void sendKnock() { + void sendKnock_success() { // Given AuthMember member = AuthorizationThreadLocal.getAuthMember(); willDoNothing().given(roomService).validateRoomById(any(Long.class)); - given(notificationRepository.existsKnockByKey(any(String.class))).willReturn(false); given(fcmService.findTokenByMemberId(any(Long.class))).willReturn("FCM-TOKEN"); + given(notificationRepository.existsKnockByKey(any(Long.class), any(Long.class), any(Long.class))) + .willReturn(false); // When notificationService.sendKnock(member, 2L, 1L); // Then verify(fcmService).sendAsync(any(String.class), any(String.class)); - verify(notificationRepository).saveKnock(any(String.class)); + verify(notificationRepository).saveKnock(any(Long.class), any(Long.class), any(Long.class)); } @WithMember @@ -79,6 +76,7 @@ void sendKnock() { void sendKnock_Room_NotFoundException() { // Given AuthMember member = AuthorizationThreadLocal.getAuthMember(); + willThrow(NotFoundException.class).given(roomService).validateRoomById(any(Long.class)); // When & Then @@ -94,7 +92,8 @@ void sendKnock_FcmToken_NotFoundException() { AuthMember member = AuthorizationThreadLocal.getAuthMember(); willDoNothing().given(roomService).validateRoomById(any(Long.class)); - given(notificationRepository.existsKnockByKey(any(String.class))).willReturn(false); + given(notificationRepository.existsKnockByKey(any(Long.class), any(Long.class), any(Long.class))) + .willReturn(false); given(fcmService.findTokenByMemberId(any(Long.class))) .willThrow(new NotFoundException(ErrorMessage.NOT_FOUND_FCM_TOKEN)); @@ -112,7 +111,8 @@ void sendKnock_ConflictException() { AuthMember member = AuthorizationThreadLocal.getAuthMember(); willDoNothing().given(roomService).validateRoomById(any(Long.class)); - given(notificationRepository.existsKnockByKey(any(String.class))).willReturn(true); + given(notificationRepository.existsKnockByKey(any(Long.class), any(Long.class), any(Long.class))) + .willReturn(true); // When & Then assertThatThrownBy(() -> notificationService.sendKnock(member, 1L, 1L)) @@ -123,7 +123,7 @@ void sendKnock_ConflictException() { @DisplayName("특정 인증 시간에 해당하는 방 사용자들에게 알림을 성공적으로 보낸다. - Void") @MethodSource("com.moabam.support.fixture.ParticipantFixture#provideParticipants") @ParameterizedTest - void sendCertificationTime(List participants) { + void sendCertificationTime_success(List participants) { // Given given(participantSearchRepository.findAllByRoomCertifyTime(any(Integer.class))).willReturn(participants); given(fcmService.findTokenByMemberId(any(Long.class))).willReturn("FCM-TOKEN"); @@ -161,7 +161,8 @@ void getMyKnockStatusInRoom_knocked(List participants) { // Given AuthMember member = AuthorizationThreadLocal.getAuthMember(); - given(notificationRepository.existsKnockByKey(any(String.class))).willReturn(true); + given(notificationRepository.existsKnockByKey(any(Long.class), any(Long.class), any(Long.class))) + .willReturn(true); // When List actual = notificationService.getMyKnockStatusInRoom(member.id(), 1L, participants); @@ -178,8 +179,8 @@ void getMyKnockStatusInRoom_notKnocked(List participants) { // Given AuthMember member = AuthorizationThreadLocal.getAuthMember(); - // given - given(notificationRepository.existsKnockByKey(any(String.class))).willReturn(false); + given(notificationRepository.existsKnockByKey(any(Long.class), any(Long.class), any(Long.class))) + .willReturn(false); // When List actual = notificationService.getMyKnockStatusInRoom(member.id(), 1L, participants); diff --git a/src/test/java/com/moabam/api/application/room/RoomCertificationServiceTest.java b/src/test/java/com/moabam/api/application/room/CertificationServiceTest.java similarity index 86% rename from src/test/java/com/moabam/api/application/room/RoomCertificationServiceTest.java rename to src/test/java/com/moabam/api/application/room/CertificationServiceTest.java index a103d1e9..ee3bacb1 100644 --- a/src/test/java/com/moabam/api/application/room/RoomCertificationServiceTest.java +++ b/src/test/java/com/moabam/api/application/room/CertificationServiceTest.java @@ -1,8 +1,10 @@ package com.moabam.api.application.room; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.BDDMockito.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.lenient; +import static org.mockito.BDDMockito.spy; import java.time.LocalDate; import java.time.LocalDateTime; @@ -18,12 +20,9 @@ import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.multipart.MultipartFile; import com.moabam.api.application.image.ImageService; import com.moabam.api.application.member.MemberService; -import com.moabam.api.domain.image.ImageType; import com.moabam.api.domain.member.Member; import com.moabam.api.domain.room.DailyMemberCertification; import com.moabam.api.domain.room.DailyRoomCertification; @@ -43,10 +42,10 @@ import com.moabam.support.fixture.RoomFixture; @ExtendWith(MockitoExtension.class) -class RoomCertificationServiceTest { +class CertificationServiceTest { @InjectMocks - private RoomCertificationService roomCertificationService; + private CertificationService certificationService; @Mock private MemberService memberService; @@ -118,14 +117,12 @@ void already_certified_room_routine_success() { // given List routines = RoomFixture.routines(room); DailyRoomCertification dailyRoomCertification = RoomFixture.dailyRoomCertification(roomId, today); - MockMultipartFile image = RoomFixture.makeMultipartFile1(); - List images = List.of(image, image, image); List uploadImages = new ArrayList<>(); uploadImages.add("https://image.moabam.com/certifications/20231108/1_asdfsdfxcv-4815vcx-asfd"); uploadImages.add("https://image.moabam.com/certifications/20231108/2_asdfsdfxcv-4815vcx-asfd"); - given(imageService.uploadImages(images, ImageType.CERTIFICATION)).willReturn(uploadImages); given(clockHolder.times()).willReturn(LocalDateTime.now().withHour(9).withMinute(58)); + given(clockHolder.date()).willReturn(today); given(participantSearchRepository.findOne(memberId, roomId)).willReturn(Optional.of(participant)); given(memberService.getById(memberId)).willReturn(member1); given(routineRepository.findById(1L)).willReturn(Optional.of(routines.get(0))); @@ -134,7 +131,7 @@ void already_certified_room_routine_success() { Optional.of(dailyRoomCertification)); // when - roomCertificationService.certifyRoom(memberId, roomId, images); + certificationService.certifyRoom(memberId, roomId, uploadImages); // then assertThat(member1.getBug().getMorningBug()).isEqualTo(12); @@ -146,16 +143,14 @@ void already_certified_room_routine_success() { void not_certified_room_routine_success() { // given List routines = RoomFixture.routines(room); - MockMultipartFile image = RoomFixture.makeMultipartFile1(); List dailyMemberCertifications = RoomFixture.dailyMemberCertifications(roomId, participant); - List images = List.of(image, image, image); List uploadImages = new ArrayList<>(); uploadImages.add("https://image.moabam.com/certifications/20231108/1_asdfsdfxcv-4815vcx-asfd"); uploadImages.add("https://image.moabam.com/certifications/20231108/2_asdfsdfxcv-4815vcx-asfd"); - given(imageService.uploadImages(images, ImageType.CERTIFICATION)).willReturn(uploadImages); given(clockHolder.times()).willReturn(LocalDateTime.now().withHour(9).withMinute(58)); + given(clockHolder.date()).willReturn(today); given(participantSearchRepository.findOne(memberId, roomId)).willReturn(Optional.of(participant)); given(memberService.getById(memberId)).willReturn(member1); given(routineRepository.findById(1L)).willReturn(Optional.of(routines.get(0))); @@ -167,7 +162,7 @@ void not_certified_room_routine_success() { given(memberService.getRoomMembers(anyList())).willReturn(List.of(member1, member2, member3)); // when - roomCertificationService.certifyRoom(memberId, roomId, images); + certificationService.certifyRoom(memberId, roomId, uploadImages); // then assertThat(member1.getBug().getMorningBug()).isEqualTo(12); diff --git a/src/test/java/com/moabam/api/application/room/RoomServiceTest.java b/src/test/java/com/moabam/api/application/room/RoomServiceTest.java index fdecd0e5..b0038193 100644 --- a/src/test/java/com/moabam/api/application/room/RoomServiceTest.java +++ b/src/test/java/com/moabam/api/application/room/RoomServiceTest.java @@ -131,7 +131,7 @@ void room_manager_mandate_success() { given(memberService.getById(2L)).willReturn(member); // when - roomService.mandateRoomManager(managerId, room.getId(), memberId); + roomService.mandateManager(managerId, room.getId(), memberId); // then assertThat(managerParticipant.isManager()).isFalse(); @@ -157,7 +157,7 @@ void room_manager_mandate_fail() { Optional.of(managerParticipant)); // when, then - assertThatThrownBy(() -> roomService.mandateRoomManager(managerId, 1L, memberId)) + assertThatThrownBy(() -> roomService.mandateManager(managerId, 1L, memberId)) .isInstanceOf(ForbiddenException.class); } } diff --git a/src/test/java/com/moabam/api/application/room/RoomSearchServiceTest.java b/src/test/java/com/moabam/api/application/room/SearchServiceTest.java similarity index 91% rename from src/test/java/com/moabam/api/application/room/RoomSearchServiceTest.java rename to src/test/java/com/moabam/api/application/room/SearchServiceTest.java index f1239539..70e26d2f 100644 --- a/src/test/java/com/moabam/api/application/room/RoomSearchServiceTest.java +++ b/src/test/java/com/moabam/api/application/room/SearchServiceTest.java @@ -26,18 +26,18 @@ import com.moabam.api.domain.room.repository.ParticipantSearchRepository; import com.moabam.api.domain.room.repository.RoomRepository; import com.moabam.api.domain.room.repository.RoomSearchRepository; -import com.moabam.api.domain.room.repository.RoutineSearchRepository; +import com.moabam.api.domain.room.repository.RoutineRepository; +import com.moabam.api.dto.room.GetAllRoomsResponse; import com.moabam.api.dto.room.MyRoomsResponse; import com.moabam.api.dto.room.RoomsHistoryResponse; -import com.moabam.api.dto.room.SearchAllRoomsResponse; import com.moabam.global.common.util.ClockHolder; import com.moabam.support.fixture.RoomFixture; @ExtendWith(MockitoExtension.class) -class RoomSearchServiceTest { +class SearchServiceTest { @InjectMocks - private RoomSearchService roomSearchService; + private SearchService searchService; @Mock private CertificationsSearchRepository certificationsSearchRepository; @@ -46,7 +46,7 @@ class RoomSearchServiceTest { private ParticipantSearchRepository participantSearchRepository; @Mock - private RoutineSearchRepository routineSearchRepository; + private RoutineRepository routineRepository; @Mock private RoomSearchRepository roomSearchRepository; @@ -55,7 +55,7 @@ class RoomSearchServiceTest { private MemberService memberService; @Mock - private RoomCertificationService certificationService; + private CertificationService certificationService; @Mock private RoomRepository roomRepository; @@ -91,10 +91,10 @@ void get_my_rooms_success() { given(certificationService.existsRoomCertification(room2.getId(), today)).willReturn(false); given(certificationService.existsRoomCertification(room3.getId(), today)).willReturn(false); - given(clockHolder.times()).willReturn(LocalDateTime.now()); + given(clockHolder.date()).willReturn(LocalDate.now()); // when - MyRoomsResponse myRooms = roomSearchService.getMyRooms(memberId); + MyRoomsResponse myRooms = searchService.getMyRooms(memberId); // then assertThat(myRooms.participatingRooms()).hasSize(3); @@ -133,7 +133,7 @@ void get_my_join_history_success() { given(participantSearchRepository.findAllParticipantsByMemberId(memberId)).willReturn(participants); // when - RoomsHistoryResponse response = roomSearchService.getJoinHistory(memberId); + RoomsHistoryResponse response = searchService.getJoinHistory(memberId); // then assertThat(response.roomHistory()).hasSize(3); @@ -253,16 +253,16 @@ void search_all_morning_night_rooms_success() { routine28); given(roomSearchRepository.findAllWithNoOffset(null, null)).willReturn(rooms); - given(routineSearchRepository.findAllByRoomIds(anyList())).willReturn(routines); + given(routineRepository.findAllByRoomIdIn(anyList())).willReturn(routines); // when - SearchAllRoomsResponse searchAllRoomsResponse = roomSearchService.searchAllRooms(null, null); + GetAllRoomsResponse getAllRoomsResponse = searchService.getAllRooms(null, null); // then - assertThat(searchAllRoomsResponse.hasNext()).isTrue(); - assertThat(searchAllRoomsResponse.rooms()).hasSize(10); - assertThat(searchAllRoomsResponse.rooms().get(0).id()).isEqualTo(1L); - assertThat(searchAllRoomsResponse.rooms().get(9).id()).isEqualTo(10L); + assertThat(getAllRoomsResponse.hasNext()).isTrue(); + assertThat(getAllRoomsResponse.rooms()).hasSize(10); + assertThat(getAllRoomsResponse.rooms().get(0).id()).isEqualTo(1L); + assertThat(getAllRoomsResponse.rooms().get(9).id()).isEqualTo(10L); } @DisplayName("아침, 저녁 전체 방 조회 성공, 마지막 페이 조회, 다음 페이지 없음") @@ -306,16 +306,16 @@ void search_last_page_all_morning_night_rooms_success() { routine28); given(roomSearchRepository.findAllWithNoOffset(null, 10L)).willReturn(rooms); - given(routineSearchRepository.findAllByRoomIds(anyList())).willReturn(routines); + given(routineRepository.findAllByRoomIdIn(anyList())).willReturn(routines); // when - SearchAllRoomsResponse searchAllRoomsResponse = roomSearchService.searchAllRooms(null, 10L); + GetAllRoomsResponse getAllRoomsResponse = searchService.getAllRooms(null, 10L); // then - assertThat(searchAllRoomsResponse.hasNext()).isFalse(); - assertThat(searchAllRoomsResponse.rooms()).hasSize(4); - assertThat(searchAllRoomsResponse.rooms().get(0).id()).isEqualTo(11L); - assertThat(searchAllRoomsResponse.rooms().get(3).id()).isEqualTo(14L); + assertThat(getAllRoomsResponse.hasNext()).isFalse(); + assertThat(getAllRoomsResponse.rooms()).hasSize(4); + assertThat(getAllRoomsResponse.rooms().get(0).id()).isEqualTo(11L); + assertThat(getAllRoomsResponse.rooms().get(3).id()).isEqualTo(14L); } @DisplayName("전체 방 제목, 방장 이름, 루틴 내용으로 검색 성공 - 최초 조회") @@ -405,13 +405,13 @@ void search_room_by_title_manager_nickname_routine_success() { routine25, routine26, routine27, routine28); given(roomRepository.searchByKeyword("번째")).willReturn(rooms); - given(routineSearchRepository.findAllByRoomIds(anyList())).willReturn(routines); + given(routineRepository.findAllByRoomIdIn(anyList())).willReturn(routines); // when - SearchAllRoomsResponse searchAllRoomsResponse = roomSearchService.search("번째", null, null); + GetAllRoomsResponse getAllRoomsResponse = searchService.searchRooms("번째", null, null); // then - assertThat(searchAllRoomsResponse.hasNext()).isTrue(); - assertThat(searchAllRoomsResponse.rooms()).hasSize(10); + assertThat(getAllRoomsResponse.hasNext()).isTrue(); + assertThat(getAllRoomsResponse.rooms()).hasSize(10); } } diff --git a/src/test/java/com/moabam/api/domain/coupon/CouponTest.java b/src/test/java/com/moabam/api/domain/coupon/CouponTest.java index 2ab0e438..e1e146e3 100644 --- a/src/test/java/com/moabam/api/domain/coupon/CouponTest.java +++ b/src/test/java/com/moabam/api/domain/coupon/CouponTest.java @@ -13,9 +13,9 @@ class CouponTest { - @DisplayName("쿠폰이 정상적으로 생성된다. - Coupon") + @DisplayName("쿠폰이 성공적으로 생성된다. - Coupon") @Test - void coupon() { + void coupon_success() { // Given LocalDate startAt = LocalDate.of(2023, 2, 1); LocalDate openAt = LocalDate.of(2023, 1, 1); @@ -44,7 +44,7 @@ void coupon() { @DisplayName("쿠폰 보너스 포인트가 1보다 작다. - BadRequestException") @Test - void coupon_validatePoint_Point_BadRequestException() { + void validatePoint_BadRequestException() { // When& Then assertThatThrownBy(() -> CouponFixture.coupon(0, 1)) .isInstanceOf(BadRequestException.class) @@ -53,7 +53,7 @@ void coupon_validatePoint_Point_BadRequestException() { @DisplayName("쿠폰 재고가 1보다 작다. - BadRequestException") @Test - void coupon_validatePoint_Stock_BadRequestException() { + void validateStock_BadRequestException() { // When& Then assertThatThrownBy(() -> CouponFixture.coupon(1, 0)) .isInstanceOf(BadRequestException.class) diff --git a/src/test/java/com/moabam/api/domain/coupon/CouponTypeTest.java b/src/test/java/com/moabam/api/domain/coupon/CouponTypeTest.java index 406bc061..960df900 100644 --- a/src/test/java/com/moabam/api/domain/coupon/CouponTypeTest.java +++ b/src/test/java/com/moabam/api/domain/coupon/CouponTypeTest.java @@ -10,9 +10,9 @@ class CouponTypeTest { - @DisplayName("존재하는 쿠폰을 가져온다. - CouponType") + @DisplayName("존재하는 쿠폰을 성공적으로 가져온다. - CouponType") @Test - void couponType_from() { + void from_success() { // When CouponType actual = CouponType.from(CouponType.GOLDEN_COUPON.getName()); @@ -22,7 +22,7 @@ void couponType_from() { @DisplayName("존재하지 않는 쿠폰을 가져온다. - NotFoundException") @Test - void couponType_from_NotFoundException() { + void from_NotFoundException() { // When & Then assertThatThrownBy(() -> CouponType.from("Not-Coupon")) .isInstanceOf(NotFoundException.class) diff --git a/src/test/java/com/moabam/api/domain/coupon/CouponWalletTest.java b/src/test/java/com/moabam/api/domain/coupon/CouponWalletTest.java index d1b7ee46..13bcd9e5 100644 --- a/src/test/java/com/moabam/api/domain/coupon/CouponWalletTest.java +++ b/src/test/java/com/moabam/api/domain/coupon/CouponWalletTest.java @@ -9,17 +9,14 @@ class CouponWalletTest { - @DisplayName("쿠폰 지갑 엔티티를 생성한다. - Void") + @DisplayName("쿠폰 지갑 엔티티를 성공적으로 생성한다. - Void") @Test - void couponWallet() { + void couponWallet_success() { // Given Coupon coupon = CouponFixture.coupon("CouponName", 1, 2); // When - CouponWallet actual = CouponWallet.builder() - .memberId(1L) - .coupon(coupon) - .build(); + CouponWallet actual = CouponWallet.create(1L, coupon); // Then assertThat(actual.getMemberId()).isEqualTo(1L); diff --git a/src/test/java/com/moabam/api/domain/coupon/repository/CouponManageRepositoryTest.java b/src/test/java/com/moabam/api/domain/coupon/repository/CouponManageRepositoryTest.java new file mode 100644 index 00000000..2dd66862 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/coupon/repository/CouponManageRepositoryTest.java @@ -0,0 +1,172 @@ +package com.moabam.api.domain.coupon.repository; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +import java.util.Set; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.redis.core.ZSetOperations.TypedTuple; + +import com.moabam.api.infrastructure.redis.ValueRedisRepository; +import com.moabam.api.infrastructure.redis.ZSetRedisRepository; + +@ExtendWith(MockitoExtension.class) +class CouponManageRepositoryTest { + + @InjectMocks + CouponManageRepository couponManageRepository; + + @Mock + ZSetRedisRepository zSetRedisRepository; + + @Mock + ValueRedisRepository valueRedisRepository; + + @DisplayName("쿠폰 대기열에 사용자가 성공적으로 등록된다. - Void") + @Test + void addIfAbsentQueue_success() { + // When + couponManageRepository.addIfAbsentQueue("couponName", 1L, 1); + + // Then + verify(zSetRedisRepository).addIfAbsent(any(String.class), any(Long.class), any(double.class)); + } + + @DisplayName("쿠폰명이 Null인 대기열에 사용자를 등록한다.- NullPointerException") + @Test + void addIfAbsentQueue_couponName_NullPointerException() { + // When & Then + assertThatThrownBy(() -> couponManageRepository.addIfAbsentQueue(null, 1L, 1)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("쿠폰 대기열에 사용자 ID가 Null인 사용자를 등록한다. - NullPointerException") + @Test + void addIfAbsentQueue_memberId_NullPointerException() { + // When & Then + assertThatThrownBy(() -> couponManageRepository.addIfAbsentQueue("couponName", null, 1)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("쿠폰 대기열에서 성공적으로 10명을 꺼내고 삭제한다.") + @MethodSource("com.moabam.support.fixture.CouponFixture#provideTypedTuples") + @ParameterizedTest + void popMinQueue_success(Set> tuples) { + // Given + given(zSetRedisRepository.popMin(any(String.class), any(long.class))).willReturn(tuples); + + // When + Set actual = couponManageRepository.popMinQueue("couponName", 10); + + // Then + assertThat(actual).hasSize(10); + } + + @DisplayName("쿠폰명이 Null인 대기열에서 사용자를 꺼낸다. - NullPointerException") + @Test + void popMinQueue_NullPointerException() { + // When & Then + assertThatThrownBy(() -> couponManageRepository.popMinQueue(null, 10)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("쿠폰 대기열을 성공적으로 삭제한다. - Void") + @Test + void deleteQueue_success() { + // When + couponManageRepository.deleteQueue("couponName"); + + // Then + verify(valueRedisRepository).delete(any(String.class)); + } + + @DisplayName("쿠폰명이 Null인 대기열을 삭제한다. - NullPointerException") + @Test + void deleteQueue_NullPointerException() { + // When & Then + assertThatThrownBy(() -> couponManageRepository.deleteQueue(null)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("쿠폰의 할당된 재고를 성공적으로 증가시킨다. - int") + @Test + void increaseIssuedStock_success() { + // Given + given(valueRedisRepository.increment(any(String.class))).willReturn(77L); + + // When + int actual = couponManageRepository.increaseIssuedStock("couponName"); + + // Then + assertThat(actual).isEqualTo(77); + } + + @DisplayName("쿠폰명이 Null인 쿠폰의 할당된 재고를 증가시킨다. - NullPointerException") + @Test + void increaseIssuedStock_NullPointerException() { + // When & Then + assertThatThrownBy(() -> couponManageRepository.increaseIssuedStock(null)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("쿠폰의 현재 할당된 재고를 성공적으로 조회한다. - int") + @Test + void getIssuedStock_success() { + // Given + given(valueRedisRepository.get(any(String.class))).willReturn("1"); + + // When + int actual = couponManageRepository.getIssuedStock("couponName"); + + // Then + assertThat(actual).isEqualTo(1); + } + + @DisplayName("쿠폰의 현재 할당된 재고가 없어서 0이 조회된다. - int") + @Test + void getIssuedStock_zero() { + // Given + given(valueRedisRepository.get(any(String.class))).willReturn(null); + + // When + int actual = couponManageRepository.getIssuedStock("couponName"); + + // Then + assertThat(actual).isZero(); + } + + @DisplayName("쿠폰명이 Null인 쿠폰의 할당된 재고를 조회한다. - NullPointerException") + @Test + void getIssuedStock_NullPointerException() { + // When & Then + assertThatThrownBy(() -> couponManageRepository.getIssuedStock(null)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("할당된 쿠폰 재고를 성공적으로 삭제한다. - Void") + @Test + void deleteIssuedStock_success() { + // When + couponManageRepository.deleteIssuedStock("couponName"); + + // Then + verify(valueRedisRepository).delete(any(String.class)); + } + + @DisplayName("쿠폰명이 Null인 할당된 쿠폰 재고를 삭제한다. - NullPointerException") + @Test + void deleteIssuedStock_NullPointerException() { + // When & Then + assertThatThrownBy(() -> couponManageRepository.deleteIssuedStock(null)) + .isInstanceOf(NullPointerException.class); + } +} diff --git a/src/test/java/com/moabam/api/domain/coupon/repository/CouponQueueRepositoryTest.java b/src/test/java/com/moabam/api/domain/coupon/repository/CouponQueueRepositoryTest.java deleted file mode 100644 index 9c48c266..00000000 --- a/src/test/java/com/moabam/api/domain/coupon/repository/CouponQueueRepositoryTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.moabam.api.domain.coupon.repository; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -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.infrastructure.redis.ZSetRedisRepository; - -@ExtendWith(MockitoExtension.class) -class CouponQueueRepositoryTest { - - @InjectMocks - private CouponQueueRepository couponQueueRepository; - - @Mock - private ZSetRedisRepository zSetRedisRepository; - - @DisplayName("특정 쿠폰의 대기열에 사용자가 성공적으로 등록된다. - Void") - @Test - void addQueue() { - // When - couponQueueRepository.addIfAbsent("couponName", "memberNickname", 1); - - // Then - verify(zSetRedisRepository).addIfAbsent(any(String.class), any(String.class), any(Double.class)); - } - - @DisplayName("특정 쿠폰의 대기열에 사용자 등록 시, 필요한 값이 NULL 이다.- NullPointerException") - @Test - void addQueue_NullPointerException() { - // When & Then - assertThatThrownBy(() -> couponQueueRepository.addIfAbsent(null, "value", 1)) - .isInstanceOf(NullPointerException.class); - } - - @DisplayName("특정 쿠폰을 발급한 사용자가 3명이다. - Long") - @Test - void queueSize() { - // Given - given(zSetRedisRepository.size(any(String.class))).willReturn(3L); - - // When - long actual = couponQueueRepository.size("key"); - - // Then - assertThat(actual).isEqualTo(3); - } - - @DisplayName("특정 쿠폰을 발급한 사용자 수 조회 시, 필요한 값이 Null이다. - NullPointerException") - @Test - void queueSize_NullPointerException() { - // When & Then - assertThatThrownBy(() -> couponQueueRepository.size(null)) - .isInstanceOf(NullPointerException.class); - } -} diff --git a/src/test/java/com/moabam/api/domain/coupon/repository/CouponSearchRepositoryTest.java b/src/test/java/com/moabam/api/domain/coupon/repository/CouponSearchRepositoryTest.java index 22deb4da..67fdf033 100644 --- a/src/test/java/com/moabam/api/domain/coupon/repository/CouponSearchRepositoryTest.java +++ b/src/test/java/com/moabam/api/domain/coupon/repository/CouponSearchRepositoryTest.java @@ -22,15 +22,15 @@ class CouponSearchRepositoryTest { @Autowired - private CouponRepository couponRepository; + CouponRepository couponRepository; @Autowired - private CouponSearchRepository couponSearchRepository; + CouponSearchRepository couponSearchRepository; - @DisplayName("발급 가능한 쿠폰을 조회한다. - List") + @DisplayName("발급 가능한 쿠폰을 성공적으로 조회한다. - List") @MethodSource("com.moabam.support.fixture.CouponFixture#provideCoupons") @ParameterizedTest - void findAllByStatus(List coupons) { + void findAllByStatus_success(List coupons) { // Given CouponStatusRequest request = CouponFixture.couponStatusRequest(false, false); LocalDate now = LocalDate.of(2023, 7, 1); diff --git a/src/test/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepositoryTest.java b/src/test/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepositoryTest.java index f1066346..1d8d6526 100644 --- a/src/test/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepositoryTest.java +++ b/src/test/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepositoryTest.java @@ -3,13 +3,20 @@ import static com.moabam.support.fixture.CouponFixture.*; import static org.assertj.core.api.Assertions.*; +import java.util.List; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; import com.moabam.api.domain.coupon.Coupon; import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.model.ErrorMessage; import com.moabam.support.annotation.QuerydslRepositoryTest; +import com.moabam.support.fixture.CouponFixture; @QuerydslRepositoryTest class CouponWalletSearchRepositoryTest { @@ -23,17 +30,83 @@ class CouponWalletSearchRepositoryTest { @Autowired private CouponWalletSearchRepository couponWalletSearchRepository; - @DisplayName("회원의 특정 쿠폰 지갑을 조회한다.") + @DisplayName("나의 쿠폰함의 특정 쿠폰을 조회한다.. - List") + @Test + void findAllByCouponIdAndMemberId_success() { + // Given + Coupon coupon = couponRepository.save(CouponFixture.coupon()); + CouponWallet couponWallet = couponWalletRepository.save(CouponWallet.create(1L, coupon)); + + // When + List actual = couponWalletSearchRepository.findAllByCouponIdAndMemberId(coupon.getId(), 1L); + + // Then + assertThat(actual).hasSize(1); + assertThat(actual.get(0).getCoupon().getName()).isEqualTo(coupon.getName()); + assertThat(actual.get(0).getMemberId()).isEqualTo(couponWallet.getMemberId()); + } + + @DisplayName("ID가 1인 회원은 쿠폰 1개를 가지고 있다. - List") + @MethodSource("com.moabam.support.fixture.CouponWalletFixture#provideCouponWalletAll") + @ParameterizedTest + void findAllByCouponIdAndMemberId_Id1_success(List couponWallets) { + // Given + couponWallets.forEach(couponWallet -> { + Coupon coupon = couponRepository.save(couponWallet.getCoupon()); + couponWalletRepository.save(CouponWallet.create(couponWallet.getMemberId(), coupon)); + }); + + // When + List actual = couponWalletSearchRepository.findAllByCouponIdAndMemberId(null, 1L); + + // Then + assertThat(actual).hasSize(1); + } + + @DisplayName("ID가 2인 회원은 쿠폰 ID가 777인 쿠폰을 가지고 있지 않다. - List") + @MethodSource("com.moabam.support.fixture.CouponWalletFixture#provideCouponWalletAll") + @ParameterizedTest + void findAllByCouponIdAndMemberId_Id2_notCouponId777(List couponWallets) { + // Given + couponWallets.forEach(couponWallet -> { + Coupon coupon = couponRepository.save(couponWallet.getCoupon()); + couponWalletRepository.save(CouponWallet.create(couponWallet.getMemberId(), coupon)); + }); + + // When + List actual = couponWalletSearchRepository.findAllByCouponIdAndMemberId(777L, 2L); + + // Then + assertThat(actual).isEmpty(); + } + + @DisplayName("ID가 3인 회원은 쿠폰 3개를 가지고 있다. - List") + @MethodSource("com.moabam.support.fixture.CouponWalletFixture#provideCouponWalletAll") + @ParameterizedTest + void findAllByCouponIdAndMemberId_Id3_success(List couponWallets) { + // Given + couponWallets.forEach(couponWallet -> { + Coupon coupon = couponRepository.save(couponWallet.getCoupon()); + couponWalletRepository.save(CouponWallet.create(couponWallet.getMemberId(), coupon)); + }); + + // When + List actual = couponWalletSearchRepository.findAllByCouponIdAndMemberId(null, 3L); + + // Then + assertThat(actual).hasSize(3); + } + + @DisplayName("회원의 특정 쿠폰 지갑을 성공적으로 조회한다.") @Test - void find_by_id_and_member_id() { + void findByIdAndMemberId_success() { // given - Long id = 1L; - Long memberId = 1L; Coupon coupon = couponRepository.save(discount1000Coupon()); - couponWalletRepository.save(couponWallet(memberId, coupon)); + CouponWallet couponWallet = couponWalletRepository.save(CouponWallet.create(1L, coupon)); // when - CouponWallet actual = couponWalletSearchRepository.findByIdAndMemberId(id, memberId).orElseThrow(); + CouponWallet actual = couponWalletSearchRepository.findByIdAndMemberId(couponWallet.getId(), 1L) + .orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON_WALLET)); // then assertThat(actual.getCoupon()).isEqualTo(coupon); 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 8e3ae4f8..b3168af3 100644 --- a/src/test/java/com/moabam/api/domain/entity/MemberTest.java +++ b/src/test/java/com/moabam/api/domain/entity/MemberTest.java @@ -10,6 +10,7 @@ import com.moabam.api.domain.bug.Bug; import com.moabam.api.domain.member.Member; import com.moabam.api.domain.member.Role; +import com.moabam.api.domain.room.RoomType; import com.moabam.global.common.util.BaseImageUrl; import com.moabam.support.fixture.MemberFixture; @@ -40,7 +41,7 @@ void create_member_noImage_success() { .build(); assertAll( - () -> assertThat(member.getProfileImage()).isEqualTo(BaseImageUrl.PROFILE_URL), + () -> assertThat(member.getProfileImage()).isEqualTo(BaseImageUrl.MEMBER_PROFILE_URL), () -> assertThat(member.getRole()).isEqualTo(Role.USER), () -> assertThat(member.getBug().getNightBug()).isZero(), () -> assertThat(member.getBug().getGoldenBug()).isZero(), @@ -82,10 +83,10 @@ void member_room_enter_success() { // when int beforeMorningCount = member.getCurrentMorningCount(); - member.enterMorningRoom(); + member.enterRoom(RoomType.MORNING); int beforeNightCount = member.getCurrentNightCount(); - member.enterNightRoom(); + member.enterRoom(RoomType.NIGHT); // then assertThat(member.getCurrentMorningCount()).isEqualTo(beforeMorningCount + 1); @@ -99,12 +100,12 @@ void member_room_exit_success() { Member member = MemberFixture.member(); // when - member.exitMorningRoom(); - member.exitNightRoom(); + member.exitRoom(RoomType.MORNING); + member.exitRoom(RoomType.NIGHT); // then assertThat(member.getCurrentMorningCount()).isZero(); - assertThat(member.getCurrentMorningCount()).isZero(); + assertThat(member.getCurrentNightCount()).isZero(); } } } 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..2fdd57e9 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 @@ -18,7 +18,11 @@ import com.moabam.api.domain.item.ItemType; import com.moabam.api.domain.member.Member; import com.moabam.api.domain.member.repository.MemberRepository; +import com.moabam.api.domain.room.RoomType; 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 +111,59 @@ void find_default_success() { // then assertThat(actual).isPresent().contains(inventory); } + + @DisplayName("여러 회원의 밤 타입에 적용된 인벤토리를 조회한다.") + @Test + void find_all_default_type_night_success() { + // given + Member member1 = memberRepository.save(member("1", "회원1")); + Member member2 = memberRepository.save(member("2", "회원2")); + Item item = itemRepository.save(nightMageSkin()); + Inventory inventory1 = inventoryRepository.save(inventory(member1.getId(), item)); + Inventory inventory2 = inventoryRepository.save(inventory(member2.getId(), item)); + inventory1.select(); + inventory2.select(); + + // when + List actual = inventorySearchRepository.findDefaultInventories(List.of(member1.getId(), + member2.getId()), RoomType.NIGHT.name()); + + // then + assertThat(actual).hasSize(2); + assertThat(actual.get(0).getItem().getName()).isEqualTo(nightMageSkin().getName()); + } + + @DisplayName("기본 새 찾는 쿼리") + @Nested + class FindDefaultBird { + + @DisplayName("default 가져오기 성공") + @Test + void bird_find_success() { + // given + Member member = MemberFixture.member(); + member.exitRoom(RoomType.MORNING); + 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.findDefaultSkin(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..ff6d4403 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,19 @@ 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.RoomType; 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 +39,9 @@ class MemberRepositoryTest { @Autowired RoomRepository roomRepository; + @Autowired + BadgeRepository badgeRepository; + @Autowired ParticipantRepository participantRepository; @@ -60,8 +70,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 +105,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.enterRoom(RoomType.MORNING); + 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.enterRoom(RoomType.MORNING); + 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/domain/notification/repository/NotificationRepositoryTest.java b/src/test/java/com/moabam/api/domain/notification/repository/NotificationRepositoryTest.java index 6a9a376d..681a9aa5 100644 --- a/src/test/java/com/moabam/api/domain/notification/repository/NotificationRepositoryTest.java +++ b/src/test/java/com/moabam/api/domain/notification/repository/NotificationRepositoryTest.java @@ -12,50 +12,82 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import com.moabam.api.infrastructure.redis.StringRedisRepository; +import com.moabam.api.infrastructure.redis.ValueRedisRepository; @ExtendWith(MockitoExtension.class) class NotificationRepositoryTest { @InjectMocks - private NotificationRepository notificationRepository; + NotificationRepository notificationRepository; @Mock - private StringRedisRepository stringRedisRepository; + ValueRedisRepository valueRedisRepository; @DisplayName("콕 알림이 성공적으로 저장된다. - Void") @Test - void notificationRepository_saveKnockNotification() { + void saveKnock_success() { // When - notificationRepository.saveKnock("knockKey"); + notificationRepository.saveKnock(1L, 1L, 1L); // Then - verify(stringRedisRepository).save(any(String.class), any(String.class), any(Duration.class)); + verify(valueRedisRepository).save(any(String.class), any(String.class), any(Duration.class)); } - @DisplayName("콕 알림 저장 시, 필요한 값이 NULL 이다. - NullPointerException") + @DisplayName("콕 찌르는 사용자의 ID가 Null인 콕 알림을 저장한다. - NullPointerException") @Test - void notificationRepository_saveKnockNotification_NullPointerException() { + void saveKnock_MemberId_NullPointerException() { // When & Then - assertThatThrownBy(() -> notificationRepository.saveKnock(null)) + assertThatThrownBy(() -> notificationRepository.saveKnock(null, 1L, 1L)) .isInstanceOf(NullPointerException.class); } - @DisplayName("콕 알림 여부 체크를 정상적으로 확인한다. - Boolean") + @DisplayName("콕 찌를 대상의 ID가 Null인 콕 알림을 저장한다. - NullPointerException") @Test - void notificationRepository_existsKnockByMemberId() { + void saveKnock_TargetId_NullPointerException() { + // When & Then + assertThatThrownBy(() -> notificationRepository.saveKnock(1L, null, 1L)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("방 ID가 Null인 콕 알림을 저장한다. - NullPointerException") + @Test + void saveKnock_RoomId_NullPointerException() { + // When & Then + assertThatThrownBy(() -> notificationRepository.saveKnock(1L, 2L, null)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("콕 알림 여부 체크를 성공적으로 확인한다. - Boolean") + @Test + void existsKnockByKey_success() { // When - notificationRepository.existsKnockByKey("knock key"); + notificationRepository.existsKnockByKey(1L, 1L, 1L); // Then - verify(stringRedisRepository).hasKey(any(String.class)); + verify(valueRedisRepository).hasKey(any(String.class)); + } + + @DisplayName("콕 찌르는 사용자의 ID가 Null인 콕 알림 여부를 체크한다. - NullPointerException") + @Test + void existsKnockByKey_MemberId_NullPointerException() { + // When & Then + assertThatThrownBy(() -> notificationRepository.existsKnockByKey(null, 1L, 1L)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("콕 찌를 상대 ID가 Null인 콕 알림 여부를 체크한다. - NullPointerException") + @Test + void existsKnockByKey_TargetId_NullPointerException() { + // When & Then + assertThatThrownBy(() -> notificationRepository.existsKnockByKey(1L, null, 1L)) + .isInstanceOf(NullPointerException.class); } - @DisplayName("콕 알림 여부 체크 시, 필요한 값이 NULL 이다. - NullPointerException") + @DisplayName("방 ID가 Null인 콕 알림 여부를 체크한다. - NullPointerException") @Test - void notificationRepository_existsKnockByMemberId_NullPointerException() { + void existsKnockByKey_RoomId_NullPointerException() { // When & Then - assertThatThrownBy(() -> notificationRepository.existsKnockByKey(null)) + assertThatThrownBy(() -> notificationRepository.existsKnockByKey(1L, 2L, null)) .isInstanceOf(NullPointerException.class); } } diff --git a/src/test/java/com/moabam/api/dto/coupon/CreateCouponRequestTest.java b/src/test/java/com/moabam/api/dto/coupon/CreateCouponRequestTest.java index 5fcf7561..3454611b 100644 --- a/src/test/java/com/moabam/api/dto/coupon/CreateCouponRequestTest.java +++ b/src/test/java/com/moabam/api/dto/coupon/CreateCouponRequestTest.java @@ -15,7 +15,7 @@ class CreateCouponRequestTest { @DisplayName("쿠폰 발급 가능 시작 날짜가 올바른 형식으로 입력된다. - yyyy-MM-dd") @Test - void createCouponRequest_StartAt() throws JsonProcessingException { + void startAt_success() throws JsonProcessingException { // Given ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new JavaTimeModule()); diff --git a/src/test/java/com/moabam/api/infrastructure/fcm/FcmRepositoryTest.java b/src/test/java/com/moabam/api/infrastructure/fcm/FcmRepositoryTest.java index 4c6f4367..83f2e887 100644 --- a/src/test/java/com/moabam/api/infrastructure/fcm/FcmRepositoryTest.java +++ b/src/test/java/com/moabam/api/infrastructure/fcm/FcmRepositoryTest.java @@ -13,46 +13,54 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import com.moabam.api.infrastructure.redis.StringRedisRepository; +import com.moabam.api.infrastructure.redis.ValueRedisRepository; @ExtendWith(MockitoExtension.class) class FcmRepositoryTest { @InjectMocks - private FcmRepository fcmRepository; + FcmRepository fcmRepository; @Mock - private StringRedisRepository stringRedisRepository; + ValueRedisRepository valueRedisRepository; @DisplayName("FCM 토큰이 성공적으로 저장된다. - Void") @Test - void saveToken() { + void saveToken_success() { // When fcmRepository.saveToken(1L, "value1"); // Then - verify(stringRedisRepository).save(any(String.class), any(String.class), any(Duration.class)); + verify(valueRedisRepository).save(any(String.class), any(String.class), any(Duration.class)); } - @DisplayName("FCM 토큰 저장 시, 필요한 값이 NULL 이다. - NullPointerException") + @DisplayName("ID가 Null인 사용자가 FCM 토큰을 저장한다. - NullPointerException") @Test - void saveToken_NullPointerException() { + void saveToken_MemberId_NullPointerException() { // When & Then assertThatThrownBy(() -> fcmRepository.saveToken(null, "value")) .isInstanceOf(NullPointerException.class); } + @DisplayName("토큰이 Null인 FCM 토큰을 저장한다. - NullPointerException") + @Test + void saveToken_FcmToken_NullPointerException() { + // When & Then + assertThatThrownBy(() -> fcmRepository.saveToken(1L, null)) + .isInstanceOf(NullPointerException.class); + } + @DisplayName("FCM 토큰이 성공적으로 삭제된다. - Void") @Test - void deleteTokenByMemberId() { + void deleteTokenByMemberId_success() { // When fcmRepository.deleteTokenByMemberId(1L); // Then - verify(stringRedisRepository).delete(any(String.class)); + verify(valueRedisRepository).delete(any(String.class)); } - @DisplayName("FCM 토큰 삭제 시, 필요한 값이 NULL 이다. - NullPointerException") + @DisplayName("ID가 Null인 사용자가 FCM 토큰을 삭제한다.. - NullPointerException") @Test void deleteTokenByMemberId_NullPointerException() { // When & Then @@ -62,15 +70,15 @@ void deleteTokenByMemberId_NullPointerException() { @DisplayName("FCM 토큰을 성공적으로 조회된다. - (String) FCM TOKEN") @Test - void findTokenByMemberId() { + void findTokenByMemberId_success() { // When fcmRepository.findTokenByMemberId(1L); // Then - verify(stringRedisRepository).get(any(String.class)); + verify(valueRedisRepository).get(any(String.class)); } - @DisplayName("FCM 토큰 조회 시, 필요한 값이 NULL 이다. - NullPointerException") + @DisplayName("ID가 Null인 사용자가 FCM 토큰을 조회한다. - NullPointerException") @Test void findTokenByMemberId_NullPointerException() { // When & Then @@ -80,15 +88,15 @@ void findTokenByMemberId_NullPointerException() { @DisplayName("FCM 토큰 존재 여부를 성공적으로 확인한다. - Boolean") @Test - void existsTokenByMemberId() { + void existsTokenByMemberId_success() { // When fcmRepository.existsTokenByMemberId(1L); // Then - verify(stringRedisRepository).hasKey(any(String.class)); + verify(valueRedisRepository).hasKey(any(String.class)); } - @DisplayName("FCM 토큰 존재 여부 체크 시, 필요한 값이 NULL 이다. - NullPointerException") + @DisplayName("ID가 Null인 사용자가 FCM 토큰 존재 여부를 확인한다. - NullPointerException") @Test void existsTokenByMemberId_NullPointerException() { // When & Then diff --git a/src/test/java/com/moabam/api/infrastructure/fcm/FcmServiceTest.java b/src/test/java/com/moabam/api/infrastructure/fcm/FcmServiceTest.java index 218e4ef7..c4b7cc09 100644 --- a/src/test/java/com/moabam/api/infrastructure/fcm/FcmServiceTest.java +++ b/src/test/java/com/moabam/api/infrastructure/fcm/FcmServiceTest.java @@ -20,18 +20,18 @@ class FcmServiceTest extends WithoutFilterSupporter { @Autowired - private FcmService fcmService; + FcmService fcmService; @MockBean - private FirebaseMessaging firebaseMessaging; + FirebaseMessaging firebaseMessaging; @MockBean - private FcmRepository fcmRepository; + FcmRepository fcmRepository; @WithMember @DisplayName("FCM 토큰이 성공적으로 저장된다. - Void") @Test - void saveToken() { + void saveToken_success() { // Given AuthMember authMember = AuthorizationThreadLocal.getAuthMember(); @@ -72,7 +72,7 @@ void saveToken_Null() { @DisplayName("FCM 토큰이 성공적으로 삭제된다. - Void") @Test - void deleteTokenByMemberId() { + void deleteTokenByMemberId_success() { // When fcmRepository.deleteTokenByMemberId(1L); @@ -82,7 +82,7 @@ void deleteTokenByMemberId() { @DisplayName("FCM 토큰을 성공적으로 조회된다. - (String) FCM TOKEN") @Test - void findTokenByMemberId() { + void findTokenByMemberId_success() { // When fcmRepository.findTokenByMemberId(1L); @@ -92,7 +92,7 @@ void findTokenByMemberId() { @DisplayName("비동기 FCM 알림을 성공적으로 보낸다. - Void") @Test - void sendAsync() { + void sendAsync_success() { // When fcmService.sendAsync("FCM-TOKEN", "알림"); diff --git a/src/test/java/com/moabam/api/infrastructure/redis/StringRedisRepositoryTest.java b/src/test/java/com/moabam/api/infrastructure/redis/ValueRedisRepositoryTest.java similarity index 51% rename from src/test/java/com/moabam/api/infrastructure/redis/StringRedisRepositoryTest.java rename to src/test/java/com/moabam/api/infrastructure/redis/ValueRedisRepositoryTest.java index e38fbe34..c0566697 100644 --- a/src/test/java/com/moabam/api/infrastructure/redis/StringRedisRepositoryTest.java +++ b/src/test/java/com/moabam/api/infrastructure/redis/ValueRedisRepositoryTest.java @@ -13,57 +13,74 @@ import com.moabam.global.config.EmbeddedRedisConfig; -@SpringBootTest(classes = {EmbeddedRedisConfig.class, StringRedisRepository.class}) -class StringRedisRepositoryTest { +@SpringBootTest(classes = {EmbeddedRedisConfig.class, ValueRedisRepository.class}) +class ValueRedisRepositoryTest { @Autowired - private StringRedisRepository stringRedisRepository; + ValueRedisRepository valueRedisRepository; String key = "key"; String value = "value"; + String stockKey = "key_INCR"; Duration duration = Duration.ofMillis(5000); @BeforeEach void setUp() { - stringRedisRepository.save(key, value, duration); + valueRedisRepository.save(key, value, duration); } @AfterEach void setDown() { - stringRedisRepository.delete(key); + if (valueRedisRepository.hasKey(key)) { + valueRedisRepository.delete(key); + } + + if (valueRedisRepository.hasKey(stockKey)) { + valueRedisRepository.delete(stockKey); + } } @DisplayName("레디스에 문자열 데이터가 성공적으로 저장된다. - Void") @Test - void stringRedisRepository_save() { + void save_success() { // Then - assertThat(stringRedisRepository.get(key)).isEqualTo(value); + assertThat(valueRedisRepository.get(key)).isEqualTo(value); } - @DisplayName("레디스의 특정 데이터가 성공적으로 삭제된다. - Void") + @DisplayName("레디스의 특정 데이터가 성공적으로 조회된다. - String(Value)") @Test - void stringRedisRepository_delete() { + void get_success() { // When - stringRedisRepository.delete(key); + String actual = valueRedisRepository.get(key); // Then - assertThat(stringRedisRepository.hasKey(key)).isFalse(); + assertThat(actual).isEqualTo(valueRedisRepository.get(key)); } - @DisplayName("레디스의 특정 데이터가 성공적으로 조회된다. - String(Value)") + @DisplayName("레디스의 특정 데이터 존재 여부를 성공적으로 체크한다. - Boolean") @Test - void stringRedisRepository_get() { + void hasKey_success() { + // When & Then + assertThat(valueRedisRepository.hasKey("not found key")).isFalse(); + } + + @DisplayName("레디스의 특정 데이터가 성공적으로 삭제된다. - Void") + @Test + void delete_success() { // When - String actual = stringRedisRepository.get(key); + valueRedisRepository.delete(key); // Then - assertThat(actual).isEqualTo(stringRedisRepository.get(key)); + assertThat(valueRedisRepository.hasKey(key)).isFalse(); } - @DisplayName("레디스의 특정 데이터 존재 여부를 성공적으로 체크한다. - Boolean") + @DisplayName("레디스의 특정 데이터의 값이 1 증가한다.") @Test - void stringRedisRepository_hasKey() { - // When & Then - assertThat(stringRedisRepository.hasKey("not found key")).isFalse(); + void increment_success() { + // When + Long actual = valueRedisRepository.increment(stockKey); + + // Then + assertThat(actual).isEqualTo(1L); } } diff --git a/src/test/java/com/moabam/api/infrastructure/redis/ZSetRedisRepositoryTest.java b/src/test/java/com/moabam/api/infrastructure/redis/ZSetRedisRepositoryTest.java index 0713fdff..cfc29bb4 100644 --- a/src/test/java/com/moabam/api/infrastructure/redis/ZSetRedisRepositoryTest.java +++ b/src/test/java/com/moabam/api/infrastructure/redis/ZSetRedisRepositoryTest.java @@ -2,42 +2,49 @@ import static org.assertj.core.api.Assertions.*; +import java.util.Set; + import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations.TypedTuple; import com.moabam.global.config.EmbeddedRedisConfig; -@SpringBootTest(classes = {EmbeddedRedisConfig.class, ZSetRedisRepository.class}) +@SpringBootTest(classes = {EmbeddedRedisConfig.class, ZSetRedisRepository.class, ValueRedisRepository.class}) class ZSetRedisRepositoryTest { @Autowired - private ZSetRedisRepository zSetRedisRepository; + ZSetRedisRepository zSetRedisRepository; + + @Autowired + ValueRedisRepository valueRedisRepository; @Autowired - private RedisTemplate redisTemplate; + RedisTemplate redisTemplate; String key = "key"; - String value = "value"; + Long value = 1L; @AfterEach void afterEach() { - if (zSetRedisRepository.hasKey(key)) { - zSetRedisRepository.delete(key); + if (valueRedisRepository.hasKey(key)) { + valueRedisRepository.delete(key); } } @DisplayName("레디스의 SortedSet 데이터가 성공적으로 저장된다. - Void") @Test - void setRedisRepository_addIfAbsent() { + void addIfAbsent_success() { // When zSetRedisRepository.addIfAbsent(key, value, 1); // Then - assertThat(zSetRedisRepository.size(key)).isEqualTo(1); + assertThat(valueRedisRepository.hasKey(key)).isTrue(); } @DisplayName("이미 존재하는 값을 한 번 더 저장을 시도한다. - Void") @@ -51,36 +58,54 @@ void setRedisRepository_addIfAbsent_not_update() { assertThat(redisTemplate.opsForZSet().score(key, value)).isEqualTo(1); } - @DisplayName("레디스의 특정 키의 사이즈가 성공적으로 반환된다. - int") + @Disabled + @DisplayName("저장된 데이터와 동일한 갯수만큼의 반환과 삭제를 성공적으로 시도한다. - Set>") @Test - void setRedisRepository_size() { + void popMin_same_success() { // Given - zSetRedisRepository.addIfAbsent(key, value, 1); + zSetRedisRepository.addIfAbsent(key, value + 1, 1); + zSetRedisRepository.addIfAbsent(key, value + 2, 2); + zSetRedisRepository.addIfAbsent(key, value + 3, 3); // When - long actual = zSetRedisRepository.size(key); + Set> actual = zSetRedisRepository.popMin(key, 3); // Then - assertThat(actual).isEqualTo(1); + assertThat(actual).hasSize(3); + assertThat(valueRedisRepository.hasKey(key)).isFalse(); } - @DisplayName("레디스의 특정 데이터가 성공적으로 삭제된다. - Void") + @Disabled + @DisplayName("저장된 데이터보다 많은 갯수만큼의 반환과 삭제를 성공적으로 시도한다. - Set>") @Test - void setRedisRepository_delete() { + void popMin_more_success() { // Given - zSetRedisRepository.addIfAbsent(key, value, 1); + zSetRedisRepository.addIfAbsent(key, value + 1, 1); + zSetRedisRepository.addIfAbsent(key, value + 2, 2); // When - zSetRedisRepository.delete(key); + Set> actual = zSetRedisRepository.popMin(key, 3); // Then - assertThat(zSetRedisRepository.hasKey(key)).isFalse(); + assertThat(actual).hasSize(2); } - @DisplayName("레디스의 특정 데이터 존재 여부를 성공적으로 체크한다. - Boolean") + @Disabled + @DisplayName("저장된 데이터보다 더 적은 갯수만큼의 반환과 삭제를 성공적으로 시도한다. - Set>") @Test - void setRedisRepository_hasKey() { - // When & Then - assertThat(zSetRedisRepository.hasKey("not found key")).isFalse(); + void popMin_less_success() { + // Given + zSetRedisRepository.addIfAbsent(key, value + 1, 1); + zSetRedisRepository.addIfAbsent(key, value + 2, 2); + zSetRedisRepository.addIfAbsent(key, value + 3, 3); + zSetRedisRepository.addIfAbsent(key, value + 4, 4); + zSetRedisRepository.addIfAbsent(key, value + 5, 5); + + // When + Set> actual = zSetRedisRepository.popMin(key, 3); + + // Then + assertThat(actual).hasSize(3); + assertThat(valueRedisRepository.hasKey(key)).isTrue(); } } diff --git a/src/test/java/com/moabam/api/presentation/CouponControllerTest.java b/src/test/java/com/moabam/api/presentation/CouponControllerTest.java index 87173012..880962fb 100644 --- a/src/test/java/com/moabam/api/presentation/CouponControllerTest.java +++ b/src/test/java/com/moabam/api/presentation/CouponControllerTest.java @@ -8,7 +8,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import java.time.LocalDateTime; +import java.time.LocalDate; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -28,7 +28,9 @@ import com.moabam.api.application.coupon.CouponMapper; import com.moabam.api.domain.coupon.Coupon; import com.moabam.api.domain.coupon.CouponType; +import com.moabam.api.domain.coupon.CouponWallet; import com.moabam.api.domain.coupon.repository.CouponRepository; +import com.moabam.api.domain.coupon.repository.CouponWalletRepository; import com.moabam.api.domain.member.Role; import com.moabam.api.dto.coupon.CouponStatusRequest; import com.moabam.api.dto.coupon.CreateCouponRequest; @@ -37,8 +39,9 @@ import com.moabam.support.annotation.WithMember; import com.moabam.support.common.WithoutFilterSupporter; import com.moabam.support.fixture.CouponFixture; -import com.moabam.support.fixture.CouponSnippetFixture; -import com.moabam.support.fixture.ErrorSnippetFixture; +import com.moabam.support.snippet.CouponSnippet; +import com.moabam.support.snippet.CouponWalletSnippet; +import com.moabam.support.snippet.ErrorSnippet; @Transactional @SpringBootTest @@ -47,24 +50,28 @@ class CouponControllerTest extends WithoutFilterSupporter { @Autowired - private MockMvc mockMvc; + MockMvc mockMvc; @Autowired - private ObjectMapper objectMapper; + ObjectMapper objectMapper; @Autowired - private CouponRepository couponRepository; + CouponRepository couponRepository; + + @Autowired + CouponWalletRepository couponWalletRepository; @MockBean - private ClockHolder clockHolder; + ClockHolder clockHolder; @WithMember(role = Role.ADMIN) @DisplayName("POST - 쿠폰을 성공적으로 발행한다. - Void") @Test - void create_Coupon() throws Exception { + void create_Coupon_success() throws Exception { // Given CreateCouponRequest request = CouponFixture.createCouponRequest(); - given(clockHolder.times()).willReturn(LocalDateTime.of(2022, 1, 1, 1, 1)); + + given(clockHolder.date()).willReturn(LocalDate.of(2022, 1, 1)); // When & Then mockMvc.perform(post("/admins/coupons") @@ -74,7 +81,7 @@ void create_Coupon() throws Exception { .andDo(document("admins/coupons", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), - CouponSnippetFixture.CREATE_COUPON_REQUEST)) + CouponSnippet.CREATE_COUPON_REQUEST)) .andExpect(status().isCreated()); } @@ -85,7 +92,7 @@ void create_Coupon_StartAt_BadRequestException() throws Exception { // Given CreateCouponRequest request = CouponFixture.createCouponRequest(); - given(clockHolder.times()).willReturn(LocalDateTime.of(2025, 1, 1, 1, 1)); + given(clockHolder.date()).willReturn(LocalDate.of(2025, 1, 1)); // When & Then mockMvc.perform(post("/admins/coupons") @@ -95,8 +102,8 @@ void create_Coupon_StartAt_BadRequestException() throws Exception { .andDo(document("admins/coupons", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), - CouponSnippetFixture.CREATE_COUPON_REQUEST, - ErrorSnippetFixture.ERROR_MESSAGE_RESPONSE)) + CouponSnippet.CREATE_COUPON_REQUEST, + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) .andExpect(status().isBadRequest()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.message").value(ErrorMessage.INVALID_COUPON_START_AT_PERIOD.getMessage())); @@ -110,7 +117,7 @@ void create_Coupon_OpenAt_BadRequestException() throws Exception { String couponType = CouponType.GOLDEN_COUPON.getName(); CreateCouponRequest request = CouponFixture.createCouponRequest(couponType, 1, 1); - given(clockHolder.times()).willReturn(LocalDateTime.of(2022, 1, 1, 1, 1)); + given(clockHolder.date()).willReturn(LocalDate.of(2022, 1, 1)); // When & Then mockMvc.perform(post("/admins/coupons") @@ -120,8 +127,8 @@ void create_Coupon_OpenAt_BadRequestException() throws Exception { .andDo(document("admins/coupons", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), - CouponSnippetFixture.CREATE_COUPON_REQUEST, - ErrorSnippetFixture.ERROR_MESSAGE_RESPONSE)) + CouponSnippet.CREATE_COUPON_REQUEST, + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) .andExpect(status().isBadRequest()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.message").value(ErrorMessage.INVALID_COUPON_OPEN_AT_PERIOD.getMessage())); @@ -130,7 +137,7 @@ void create_Coupon_OpenAt_BadRequestException() throws Exception { @WithMember(role = Role.ADMIN) @DisplayName("POST - 쿠폰명이 중복된 쿠폰을 발행한다. - ConflictException") @Test - void create_Coupon_ConflictException() throws Exception { + void create_Coupon_Name_ConflictException() throws Exception { // Given CreateCouponRequest request = CouponFixture.createCouponRequest(); couponRepository.save(CouponMapper.toEntity(1L, request)); @@ -143,17 +150,41 @@ void create_Coupon_ConflictException() throws Exception { .andDo(document("admins/coupons", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), - CouponSnippetFixture.CREATE_COUPON_REQUEST, - ErrorSnippetFixture.ERROR_MESSAGE_RESPONSE)) + CouponSnippet.CREATE_COUPON_REQUEST, + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) .andExpect(status().isConflict()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.message").value(ErrorMessage.CONFLICT_COUPON_NAME.getMessage())); } + @WithMember(role = Role.ADMIN) + @DisplayName("POST - 쿠폰 발행 가능 날짜가 중복된 쿠폰을 발행한다. - ConflictException") + @Test + void create_Coupon_StartAt_ConflictException() throws Exception { + // Given + CreateCouponRequest request = CouponFixture.createCouponRequest(); + Coupon conflictStartAtCoupon = CouponFixture.coupon("NotConflictName", 2, 1); + couponRepository.save(conflictStartAtCoupon); + + // When & Then + mockMvc.perform(post("/admins/coupons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("admins/coupons", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + CouponSnippet.CREATE_COUPON_REQUEST, + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isConflict()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value(ErrorMessage.CONFLICT_COUPON_START_AT.getMessage())); + } + @WithMember(role = Role.ADMIN) @DisplayName("DELETE - 쿠폰을 성공적으로 삭제한다. - Void") @Test - void delete_Coupon() throws Exception { + void delete_Coupon_success() throws Exception { // Given Coupon coupon = couponRepository.save(CouponFixture.coupon(10, 100)); @@ -176,15 +207,15 @@ void delete_Coupon_NotFoundException() throws Exception { .andDo(document("admins/coupons/couponId", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), - ErrorSnippetFixture.ERROR_MESSAGE_RESPONSE)) + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) .andExpect(status().isNotFound()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.message").value(ErrorMessage.NOT_FOUND_COUPON.getMessage())); } - @DisplayName("GET - 특정 쿠폰을 조회한다. - CouponResponse") + @DisplayName("GET - 특정 쿠폰을 성공적으로 조회한다. - CouponResponse") @Test - void getById_Coupon() throws Exception { + void getById_Coupon_success() throws Exception { // Given Coupon coupon = couponRepository.save(CouponFixture.coupon(10, 100)); @@ -194,7 +225,7 @@ void getById_Coupon() throws Exception { .andDo(document("coupons/couponId", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), - CouponSnippetFixture.COUPON_RESPONSE)) + CouponSnippet.COUPON_RESPONSE)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.id").value(coupon.getId())); @@ -209,7 +240,7 @@ void getById_Coupon_NotFoundException() throws Exception { .andDo(document("coupons/couponId", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), - ErrorSnippetFixture.ERROR_MESSAGE_RESPONSE)) + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) .andExpect(status().isNotFound()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.message").value(ErrorMessage.NOT_FOUND_COUPON.getMessage())); @@ -218,12 +249,12 @@ void getById_Coupon_NotFoundException() throws Exception { @DisplayName("POST - 모든 쿠폰을 조회한다. - List") @MethodSource("com.moabam.support.fixture.CouponFixture#provideCoupons") @ParameterizedTest - void getAllByStatus_Coupons(List coupons) throws Exception { + void getAllByStatus_Coupons_success(List coupons) throws Exception { // Given CouponStatusRequest request = CouponFixture.couponStatusRequest(true, true); List coupon = couponRepository.saveAll(coupons); - given(clockHolder.times()).willReturn(LocalDateTime.of(2022, 1, 1, 1, 1)); + given(clockHolder.date()).willReturn(LocalDate.of(2022, 1, 1)); // When & Then mockMvc.perform(post("/coupons/search") @@ -233,22 +264,22 @@ void getAllByStatus_Coupons(List coupons) throws Exception { .andDo(document("coupons/search", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), - CouponSnippetFixture.COUPON_STATUS_REQUEST, - CouponSnippetFixture.COUPON_STATUS_RESPONSE)) + CouponSnippet.COUPON_STATUS_REQUEST, + CouponSnippet.COUPON_STATUS_RESPONSE)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$", hasSize(coupon.size()))); } - @DisplayName("POST - 발급 가능한 쿠폰만 조회한다.. - List") + @DisplayName("POST - 발급 가능한 쿠폰만 조회한다. - List") @MethodSource("com.moabam.support.fixture.CouponFixture#provideCoupons") @ParameterizedTest - void getAllByStatus_Coupon(List coupons) throws Exception { + void getAllByStatus_Coupon_success(List coupons) throws Exception { // Given CouponStatusRequest request = CouponFixture.couponStatusRequest(false, false); couponRepository.saveAll(coupons); - given(clockHolder.times()).willReturn(LocalDateTime.of(2023, 3, 1, 1, 1)); + given(clockHolder.date()).willReturn(LocalDate.of(2023, 3, 1)); // When & Then mockMvc.perform(post("/coupons/search") @@ -258,21 +289,21 @@ void getAllByStatus_Coupon(List coupons) throws Exception { .andDo(document("coupons/search", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), - CouponSnippetFixture.COUPON_STATUS_REQUEST)) + CouponSnippet.COUPON_STATUS_REQUEST)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$", hasSize(1))); } - @WithMember(nickname = "member-coupon-1") - @DisplayName("POST - 쿠폰 발급 요청을 한다. - Void") + @WithMember + @DisplayName("POST - 쿠폰 발급 요청을 성공적으로 한다. - Void") @Test - void registerQueue() throws Exception { + void registerQueue_success() throws Exception { // Given Coupon couponFixture = CouponFixture.coupon(); Coupon coupon = couponRepository.save(couponFixture); - given(clockHolder.times()).willReturn(LocalDateTime.of(2023, 2, 1, 1, 1)); + given(clockHolder.date()).willReturn(LocalDate.of(2023, 2, 1)); // When & Then mockMvc.perform(post("/coupons") @@ -284,15 +315,15 @@ void registerQueue() throws Exception { .andExpect(status().isOk()); } - @WithMember(nickname = "member-coupon-2") - @DisplayName("POST - 발급 기간이 아닌 쿠폰에 발급 요청을 한다. - BadRequestException") + @WithMember + @DisplayName("POST - 발급이 가능한 쿠폰이 없는 상황에 쿠폰 발급 요청을 한다. - BadRequestException") @Test - void registerQueue_BadRequestException() throws Exception { + void registerQueue_Zero_StartAt_BadRequestException() throws Exception { // Given Coupon couponFixture = CouponFixture.coupon(); Coupon coupon = couponRepository.save(couponFixture); - given(clockHolder.times()).willReturn(LocalDateTime.of(2022, 2, 1, 1, 1)); + given(clockHolder.date()).willReturn(LocalDate.of(2022, 1, 1)); // When & Then mockMvc.perform(post("/coupons") @@ -301,7 +332,30 @@ void registerQueue_BadRequestException() throws Exception { .andDo(document("coupons", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), - ErrorSnippetFixture.ERROR_MESSAGE_RESPONSE)) + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value(ErrorMessage.INVALID_COUPON_PERIOD.getMessage())); + } + + @WithMember + @DisplayName("POST - 발급 기간이 아닌 쿠폰에 발급 요청을 한다. - BadRequestException") + @Test + void registerQueue_Not_StartAt_BadRequestException() throws Exception { + // Given + Coupon couponFixture = CouponFixture.coupon(); + couponRepository.save(couponFixture); + + given(clockHolder.date()).willReturn(LocalDate.of(2022, 2, 1)); + + // When & Then + mockMvc.perform(post("/coupons") + .param("couponName", "not start couponName")) + .andDo(print()) + .andDo(document("coupons", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) .andExpect(status().isBadRequest()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.message").value(ErrorMessage.INVALID_COUPON_PERIOD.getMessage())); @@ -312,9 +366,9 @@ void registerQueue_BadRequestException() throws Exception { @Test void registerQueue_NotFoundException() throws Exception { // Given - Coupon coupon = CouponFixture.coupon("Not found coupon name", 2, 1); + Coupon coupon = CouponFixture.coupon("Not found couponName", 2, 1); - given(clockHolder.times()).willReturn(LocalDateTime.of(2023, 2, 1, 1, 1)); + given(clockHolder.date()).willReturn(LocalDate.of(2023, 2, 1)); // When & Then mockMvc.perform(post("/coupons") @@ -323,9 +377,69 @@ void registerQueue_NotFoundException() throws Exception { .andDo(document("coupons", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), - ErrorSnippetFixture.ERROR_MESSAGE_RESPONSE)) - .andExpect(status().isNotFound()) + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isBadRequest()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$.message").value(ErrorMessage.NOT_FOUND_COUPON.getMessage())); + .andExpect(jsonPath("$.message").value(ErrorMessage.INVALID_COUPON_PERIOD.getMessage())); + } + + @WithMember + @DisplayName("GET - 나의 쿠폰함에서 특정 쿠폰을 조회한다. - List") + @Test + void getWallet_success() throws Exception { + // Given + Coupon coupon = couponRepository.save(CouponFixture.coupon()); + couponWalletRepository.save(CouponWallet.create(1L, coupon)); + + // When & Then + mockMvc.perform(get("/my-coupons/" + coupon.getId())) + .andDo(print()) + .andDo(document("my-coupons/couponId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + CouponWalletSnippet.COUPON_WALLET_RESPONSE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].id").value(coupon.getId())) + .andExpect(jsonPath("$[0].name").value(coupon.getName())); + } + + @WithMember + @DisplayName("GET - 나의 쿠폰 보관함에 있는 모든 쿠폰을 조회한다. - List") + @MethodSource("com.moabam.support.fixture.CouponWalletFixture#provideCouponWalletByCouponId1_total5") + @ParameterizedTest + void getWallet_all_success(List couponWallets) throws Exception { + // Given + couponWallets.forEach(couponWallet -> { + Coupon coupon = couponRepository.save(couponWallet.getCoupon()); + couponWalletRepository.save(CouponWallet.create(1L, coupon)); + }); + + // When & Then + mockMvc.perform(get("/my-coupons")) + .andDo(print()) + .andDo(document("my-coupons/couponId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + CouponWalletSnippet.COUPON_WALLET_RESPONSE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$", hasSize(couponWallets.size()))); + } + + @WithMember + @DisplayName("GET - 쿠폰이 없는 사용자의 쿠폰함을 조회한다. - List") + @Test + void getWallet_no_coupon() throws Exception { + // When & Then + mockMvc.perform(get("/my-coupons")) + .andDo(print()) + .andDo(document("my-coupons/couponId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()))) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$", hasSize(0))); } } diff --git a/src/test/java/com/moabam/api/presentation/MemberAuthorizeControllerTest.java b/src/test/java/com/moabam/api/presentation/MemberAuthorizeControllerTest.java index fa304209..0f5d04d7 100644 --- a/src/test/java/com/moabam/api/presentation/MemberAuthorizeControllerTest.java +++ b/src/test/java/com/moabam/api/presentation/MemberAuthorizeControllerTest.java @@ -1,10 +1,16 @@ package com.moabam.api.presentation; -import static org.mockito.BDDMockito.*; -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.MockMvcResultMatchers.*; +import static org.mockito.BDDMockito.any; +import static org.mockito.BDDMockito.doReturn; +import static org.mockito.BDDMockito.willReturn; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -37,6 +43,7 @@ import com.moabam.api.dto.auth.AuthorizationCodeResponse; import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse; import com.moabam.api.dto.auth.AuthorizationTokenResponse; +import com.moabam.global.auth.filter.CorsFilter; import com.moabam.global.common.util.GlobalConstant; import com.moabam.global.config.OAuthConfig; import com.moabam.global.error.handler.RestTemplateResponseHandler; @@ -58,6 +65,9 @@ class MemberAuthorizeControllerTest { @SpyBean AuthorizationService authorizationService; + @SpyBean + CorsFilter corsFilter; + @Autowired OAuthConfig oAuthConfig; @@ -77,6 +87,7 @@ void setUp() { RestTemplate restTemplate = restTemplateBuilder.build(); ReflectionTestUtils.setField(oAuth2AuthorizationServerRequestService, "restTemplate", restTemplate); mockRestServiceServer = MockRestServiceServer.createServer(restTemplate); + willReturn(true).given(corsFilter).secureMatch(any(), any()); } @DisplayName("인가 코드 받기 위한 로그인 페이지 요청") 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/api/presentation/NotificationControllerTest.java b/src/test/java/com/moabam/api/presentation/NotificationControllerTest.java index a1d00129..95581ddf 100644 --- a/src/test/java/com/moabam/api/presentation/NotificationControllerTest.java +++ b/src/test/java/com/moabam/api/presentation/NotificationControllerTest.java @@ -29,13 +29,13 @@ import com.moabam.api.domain.room.repository.RoomRepository; import com.moabam.api.infrastructure.fcm.FcmRepository; import com.moabam.api.infrastructure.fcm.FcmService; -import com.moabam.api.infrastructure.redis.StringRedisRepository; +import com.moabam.api.infrastructure.redis.ValueRedisRepository; import com.moabam.global.error.model.ErrorMessage; import com.moabam.support.annotation.WithMember; import com.moabam.support.common.WithoutFilterSupporter; -import com.moabam.support.fixture.ErrorSnippetFixture; import com.moabam.support.fixture.MemberFixture; import com.moabam.support.fixture.RoomFixture; +import com.moabam.support.snippet.ErrorSnippet; @Transactional @SpringBootTest @@ -43,41 +43,39 @@ @AutoConfigureRestDocs class NotificationControllerTest extends WithoutFilterSupporter { - private static final String KNOCK_KEY = "room_%s_member_%s_knocks_%s"; - @Autowired - private MockMvc mockMvc; + MockMvc mockMvc; @Autowired - private MemberRepository memberRepository; + MemberRepository memberRepository; @Autowired - private RoomRepository roomRepository; + RoomRepository roomRepository; @Autowired - private NotificationRepository notificationRepository; + NotificationRepository notificationRepository; @Autowired - private StringRedisRepository stringRedisRepository; + ValueRedisRepository valueRedisRepository; @Autowired - private FcmService fcmService; + FcmService fcmService; @Autowired - private FcmRepository fcmRepository; + FcmRepository fcmRepository; @MockBean - private FirebaseMessaging firebaseMessaging; + FirebaseMessaging firebaseMessaging; - private Member target; - private Room room; - private String knockKey; + Member target; + Room room; + String knockKey; @BeforeEach void setUp() { target = memberRepository.save(MemberFixture.member("123", "targetName")); room = roomRepository.save(RoomFixture.room()); - knockKey = String.format(KNOCK_KEY, room.getId(), 1, target.getId()); + knockKey = String.format("room_%s_member_%s_knocks_%s", room.getId(), 1, target.getId()); willReturn(null) .given(firebaseMessaging) @@ -87,13 +85,13 @@ void setUp() { @AfterEach void setDown() { fcmService.deleteTokenByMemberId(target.getId()); - stringRedisRepository.delete(knockKey); + valueRedisRepository.delete(knockKey); } @WithMember @DisplayName("POST - 성공적으로 FCM Token을 저장한다. - Void") @Test - void createFcmToken() throws Exception { + void createFcmToken_success() throws Exception { // When & Then mockMvc.perform(post("/notifications") .param("fcmToken", "FCM-TOKEN")) @@ -119,9 +117,9 @@ void createFcmToken_blank() throws Exception { } @WithMember - @DisplayName("GET - 성공적으로 상대에게 콕 알림을 보낸다. - Void") + @DisplayName("GET - 상대에게 콕 알림을 성공적으로 보낸다. - Void") @Test - void sendKnock() throws Exception { + void sendKnock_success() throws Exception { // Given fcmRepository.saveToken(target.getId(), "FCM_TOKEN"); @@ -144,7 +142,7 @@ void sendKnock_NotFoundException() throws Exception { .andDo(document("notifications/rooms/roomId/members/memberId", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), - ErrorSnippetFixture.ERROR_MESSAGE_RESPONSE)) + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) .andExpect(status().isNotFound()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.message").value(ErrorMessage.NOT_FOUND_FCM_TOKEN.getMessage())); @@ -156,7 +154,7 @@ void sendKnock_NotFoundException() throws Exception { void sendKnock_ConflictException() throws Exception { // Given fcmRepository.saveToken(target.getId(), "FCM_TOKEN"); - notificationRepository.saveKnock(knockKey); + notificationRepository.saveKnock(1L, target.getId(), room.getId()); // When & Then mockMvc.perform(get("/notifications/rooms/" + room.getId() + "/members/" + target.getId())) @@ -164,7 +162,7 @@ void sendKnock_ConflictException() throws Exception { .andDo(document("notifications/rooms/roomId/members/memberId", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), - ErrorSnippetFixture.ERROR_MESSAGE_RESPONSE)) + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) .andExpect(status().isConflict()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.message").value(ErrorMessage.CONFLICT_KNOCK.getMessage())); diff --git a/src/test/java/com/moabam/api/presentation/RoomControllerTest.java b/src/test/java/com/moabam/api/presentation/RoomControllerTest.java index 14d9049d..1e812e7b 100644 --- a/src/test/java/com/moabam/api/presentation/RoomControllerTest.java +++ b/src/test/java/com/moabam/api/presentation/RoomControllerTest.java @@ -1,11 +1,16 @@ package com.moabam.api.presentation; -import static com.moabam.api.domain.room.RoomType.*; -import static org.assertj.core.api.Assertions.*; -import static org.springframework.http.MediaType.*; -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 static com.moabam.api.domain.room.RoomType.MORNING; +import static com.moabam.api.domain.room.RoomType.NIGHT; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.time.LocalDate; import java.util.ArrayList; @@ -25,6 +30,10 @@ import org.springframework.transaction.annotation.Transactional; import com.fasterxml.jackson.databind.ObjectMapper; +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.Member; import com.moabam.api.domain.member.repository.MemberRepository; import com.moabam.api.domain.room.Certification; @@ -41,13 +50,14 @@ import com.moabam.api.domain.room.repository.ParticipantSearchRepository; import com.moabam.api.domain.room.repository.RoomRepository; import com.moabam.api.domain.room.repository.RoutineRepository; -import com.moabam.api.domain.room.repository.RoutineSearchRepository; import com.moabam.api.dto.room.CreateRoomRequest; import com.moabam.api.dto.room.EnterRoomRequest; import com.moabam.api.dto.room.ModifyRoomRequest; import com.moabam.support.annotation.WithMember; import com.moabam.support.common.WithoutFilterSupporter; import com.moabam.support.fixture.BugFixture; +import com.moabam.support.fixture.InventoryFixture; +import com.moabam.support.fixture.ItemFixture; import com.moabam.support.fixture.MemberFixture; import com.moabam.support.fixture.RoomFixture; @@ -69,9 +79,6 @@ class RoomControllerTest extends WithoutFilterSupporter { @Autowired private RoutineRepository routineRepository; - @Autowired - private RoutineSearchRepository routineSearchRepository; - @Autowired private ParticipantRepository participantRepository; @@ -90,6 +97,12 @@ class RoomControllerTest extends WithoutFilterSupporter { @Autowired private ParticipantSearchRepository participantSearchRepository; + @Autowired + private ItemRepository itemRepository; + + @Autowired + private InventoryRepository inventoryRepository; + Member member; @BeforeAll @@ -101,11 +114,11 @@ void setUp() { @AfterEach void cleanUp() { while (member.getCurrentMorningCount() > 0) { - member.exitMorningRoom(); + member.exitRoom(MORNING); } while (member.getCurrentNightCount() > 0) { - member.exitNightRoom(); + member.exitRoom(NIGHT); } } @@ -310,7 +323,7 @@ void modify_room_success() throws Exception { .andDo(print()); Room modifiedRoom = roomRepository.findById(room.getId()).orElseThrow(); - List modifiedRoutines = routineSearchRepository.findAllByRoomId(room.getId()); + List modifiedRoutines = routineRepository.findAllByRoomId(room.getId()); assertThat(modifiedRoom.getTitle()).isEqualTo("수정할 방임!"); assertThat(modifiedRoom.getCertifyTime()).isEqualTo(10); @@ -500,7 +513,7 @@ void enter_and_morning_room_over_three_fail() throws Exception { .build(); for (int i = 0; i < 3; i++) { - member.enterMorningRoom(); + member.enterRoom(MORNING); } memberRepository.save(member); @@ -528,7 +541,7 @@ void enter_and_night_room_over_three_fail() throws Exception { .build(); for (int i = 0; i < 3; i++) { - member.enterNightRoom(); + member.enterRoom(NIGHT); } memberRepository.save(member); @@ -715,7 +728,7 @@ void exit_and_decrease_morning_room_count() throws Exception { Participant participant = RoomFixture.participant(room, 1L); for (int i = 0; i < 3; i++) { - member.enterMorningRoom(); + member.enterRoom(RoomType.MORNING); } memberRepository.save(member); @@ -748,7 +761,7 @@ void exit_and_decrease_night_room_count() throws Exception { Participant participant = RoomFixture.participant(room, 1L); for (int i = 0; i < 3; i++) { - member.enterNightRoom(); + member.enterRoom(NIGHT); } memberRepository.save(member); @@ -791,8 +804,20 @@ void get_room_details_test() throws Exception { roomRepository.save(room); routineRepository.saveAll(routines); - memberRepository.save(member2); - memberRepository.save(member3); + member2 = memberRepository.save(member2); + member3 = memberRepository.save(member3); + + Item item = ItemFixture.nightMageSkin(); + + Inventory inventory1 = InventoryFixture.inventory(1L, item); + Inventory inventory2 = InventoryFixture.inventory(member2.getId(), item); + Inventory inventory3 = InventoryFixture.inventory(member3.getId(), item); + inventory1.select(); + inventory2.select(); + inventory3.select(); + + itemRepository.save(item); + inventoryRepository.saveAll(List.of(inventory1, inventory2, inventory3)); Participant participant2 = RoomFixture.participant(room, member2.getId()); Participant participant3 = RoomFixture.participant(room, member3.getId()); diff --git a/src/test/java/com/moabam/global/filter/AuthorizationFilterTest.java b/src/test/java/com/moabam/global/filter/AuthorizationFilterTest.java index d4d0e8eb..31f6303a 100644 --- a/src/test/java/com/moabam/global/filter/AuthorizationFilterTest.java +++ b/src/test/java/com/moabam/global/filter/AuthorizationFilterTest.java @@ -139,7 +139,7 @@ void filter_have_any_refresh_token_error() throws ServletException, IOException eq(null), any(UnauthorizedException.class)); } - @DisplayName("새로운 도큰 발급 성공") + @DisplayName("새로운 토큰 발급 성공") @Test void issue_new_token_success() throws ServletException, IOException { // given diff --git a/src/test/java/com/moabam/support/common/WithoutFilterSupporter.java b/src/test/java/com/moabam/support/common/WithoutFilterSupporter.java index 9645716a..ae2b827a 100644 --- a/src/test/java/com/moabam/support/common/WithoutFilterSupporter.java +++ b/src/test/java/com/moabam/support/common/WithoutFilterSupporter.java @@ -1,15 +1,18 @@ package com.moabam.support.common; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.BDDMockito.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.willReturn; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; import com.moabam.api.domain.member.Role; +import com.moabam.global.auth.filter.CorsFilter; import com.moabam.global.auth.handler.PathResolver; @ExtendWith({FilterProcessExtension.class}) @@ -18,8 +21,17 @@ public class WithoutFilterSupporter { @MockBean private PathResolver pathResolver; + @MockBean + private DefaultHandlerExceptionResolver handlerExceptionResolver; + + @SpyBean + private CorsFilter corsFilter; + @BeforeEach void setUpMock() { + willReturn(true) + .given(corsFilter).secureMatch(any(), any()); + willReturn(Optional.of(PathResolver.Path.builder() .uri("/") .role(Role.USER) 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/CouponFixture.java b/src/test/java/com/moabam/support/fixture/CouponFixture.java index 9d02a077..1c67661c 100644 --- a/src/test/java/com/moabam/support/fixture/CouponFixture.java +++ b/src/test/java/com/moabam/support/fixture/CouponFixture.java @@ -1,14 +1,17 @@ package com.moabam.support.fixture; import java.time.LocalDate; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Stream; import org.junit.jupiter.params.provider.Arguments; +import org.springframework.data.redis.core.DefaultTypedTuple; +import org.springframework.data.redis.core.ZSetOperations; import com.moabam.api.domain.coupon.Coupon; import com.moabam.api.domain.coupon.CouponType; -import com.moabam.api.domain.coupon.CouponWallet; import com.moabam.api.dto.coupon.CouponStatusRequest; import com.moabam.api.dto.coupon.CreateCouponRequest; @@ -29,6 +32,18 @@ public static Coupon coupon() { .build(); } + public static Coupon coupon(String name, int startAt) { + return Coupon.builder() + .name(name) + .point(10) + .type(CouponType.MORNING_COUPON) + .stock(100) + .startAt(LocalDate.of(2023, startAt, 1)) + .openAt(LocalDate.of(2023, 1, 1)) + .adminId(1L) + .build(); + } + public static Coupon coupon(int point, int stock) { return Coupon.builder() .name("couponName") @@ -77,13 +92,6 @@ public static Coupon discount10000Coupon() { .build(); } - public static CouponWallet couponWallet(Long memberId, Coupon coupon) { - return CouponWallet.builder() - .memberId(memberId) - .coupon(coupon) - .build(); - } - public static CreateCouponRequest createCouponRequest() { return CreateCouponRequest.builder() .name("couponName") @@ -131,4 +139,20 @@ public static Stream provideCoupons() { )) ); } + + public static Stream provideTypedTuples() { + Set> tuples = new HashSet<>(); + tuples.add(new DefaultTypedTuple<>(1L, 1.0)); + tuples.add(new DefaultTypedTuple<>(2L, 2.0)); + tuples.add(new DefaultTypedTuple<>(3L, 3.0)); + tuples.add(new DefaultTypedTuple<>(4L, 4.0)); + tuples.add(new DefaultTypedTuple<>(5L, 5.0)); + tuples.add(new DefaultTypedTuple<>(6L, 6.0)); + tuples.add(new DefaultTypedTuple<>(7L, 7.0)); + tuples.add(new DefaultTypedTuple<>(8L, 8.0)); + tuples.add(new DefaultTypedTuple<>(9L, 9.0)); + tuples.add(new DefaultTypedTuple<>(10L, 10.0)); + + return Stream.of(Arguments.of(tuples)); + } } diff --git a/src/test/java/com/moabam/support/fixture/CouponWalletFixture.java b/src/test/java/com/moabam/support/fixture/CouponWalletFixture.java new file mode 100644 index 00000000..7fa78e1f --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/CouponWalletFixture.java @@ -0,0 +1,38 @@ +package com.moabam.support.fixture; + +import static com.moabam.support.fixture.CouponFixture.*; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.params.provider.Arguments; + +import com.moabam.api.domain.coupon.CouponWallet; + +public final class CouponWalletFixture { + + public static Stream provideCouponWalletByCouponId1_total5() { + return Stream.of(Arguments.of( + List.of( + CouponWallet.create(1L, coupon("c1", 1)), + CouponWallet.create(1L, coupon("c2", 2)), + CouponWallet.create(1L, coupon("c3", 3)), + CouponWallet.create(1L, coupon("c4", 4)), + CouponWallet.create(1L, coupon("c5", 5)) + )) + ); + } + + public static Stream provideCouponWalletAll() { + return Stream.of(Arguments.of( + List.of( + CouponWallet.create(1L, coupon("c2", 2)), + CouponWallet.create(2L, coupon("c3", 3)), + CouponWallet.create(2L, coupon("c4", 4)), + CouponWallet.create(3L, coupon("c5", 5)), + CouponWallet.create(3L, coupon("c6", 6)), + CouponWallet.create(3L, coupon("c7", 7)) + )) + ); + } +} 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) + ); + } +} diff --git a/src/test/java/com/moabam/support/fixture/CouponSnippetFixture.java b/src/test/java/com/moabam/support/snippet/CouponSnippet.java similarity index 97% rename from src/test/java/com/moabam/support/fixture/CouponSnippetFixture.java rename to src/test/java/com/moabam/support/snippet/CouponSnippet.java index 40c7c63a..e1e4697f 100644 --- a/src/test/java/com/moabam/support/fixture/CouponSnippetFixture.java +++ b/src/test/java/com/moabam/support/snippet/CouponSnippet.java @@ -1,4 +1,4 @@ -package com.moabam.support.fixture; +package com.moabam.support.snippet; import static org.springframework.restdocs.payload.JsonFieldType.*; import static org.springframework.restdocs.payload.PayloadDocumentation.*; @@ -7,7 +7,7 @@ import org.springframework.restdocs.payload.ResponseFieldsSnippet; import org.springframework.restdocs.snippet.Snippet; -public final class CouponSnippetFixture { +public final class CouponSnippet { public static final RequestFieldsSnippet CREATE_COUPON_REQUEST = requestFields( fieldWithPath("name").type(STRING).description("쿠폰명"), diff --git a/src/test/java/com/moabam/support/snippet/CouponWalletSnippet.java b/src/test/java/com/moabam/support/snippet/CouponWalletSnippet.java new file mode 100644 index 00000000..f6d260be --- /dev/null +++ b/src/test/java/com/moabam/support/snippet/CouponWalletSnippet.java @@ -0,0 +1,18 @@ +package com.moabam.support.snippet; + +import static org.springframework.restdocs.payload.JsonFieldType.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; + +import org.springframework.restdocs.payload.ResponseFieldsSnippet; + +public final class CouponWalletSnippet { + + public static final ResponseFieldsSnippet COUPON_WALLET_RESPONSE = responseFields( + fieldWithPath("[].id").type(NUMBER).description("쿠폰 ID"), + fieldWithPath("[].name").type(STRING).description("쿠폰명"), + fieldWithPath("[].description").type(STRING).description("쿠폰에 대한 간단 소개 (NULL 가능)"), + fieldWithPath("[].point").type(NUMBER).description("쿠폰 사용 시, 제공하는 포인트량"), + fieldWithPath("[].type").type(STRING) + .description("쿠폰 종류 (MORNING_COUPON, NIGHT_COUPON, GOLDEN_COUPON, DISCOUNT_COUPON)") + ); +} diff --git a/src/test/java/com/moabam/support/fixture/ErrorSnippetFixture.java b/src/test/java/com/moabam/support/snippet/ErrorSnippet.java similarity index 83% rename from src/test/java/com/moabam/support/fixture/ErrorSnippetFixture.java rename to src/test/java/com/moabam/support/snippet/ErrorSnippet.java index 4c41f198..69e2c5d3 100644 --- a/src/test/java/com/moabam/support/fixture/ErrorSnippetFixture.java +++ b/src/test/java/com/moabam/support/snippet/ErrorSnippet.java @@ -1,11 +1,11 @@ -package com.moabam.support.fixture; +package com.moabam.support.snippet; import static org.springframework.restdocs.payload.JsonFieldType.*; import static org.springframework.restdocs.payload.PayloadDocumentation.*; import org.springframework.restdocs.snippet.Snippet; -public class ErrorSnippetFixture { +public class ErrorSnippet { public static final Snippet ERROR_MESSAGE_RESPONSE = responseFields( fieldWithPath("message").type(STRING).description("에러 메시지")