From a81ced1c32cd21683c14f8d1ff77923b954fdffb Mon Sep 17 00:00:00 2001 From: stoneTiger0912 Date: Wed, 20 Aug 2025 22:19:38 +0900 Subject: [PATCH 1/4] =?UTF-8?q?Feat:=20=EA=B7=B8=EB=A3=B9=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group/controller/GroupController.java | 13 +++++++ .../project/flipnote/group/entity/Group.java | 12 ++++++ .../group/exception/GroupErrorCode.java | 3 +- .../flipnote/group/model/GroupPutRequest.java | 31 +++++++++++++++ .../group/model/GroupPutResponse.java | 9 +++++ .../flipnote/group/service/GroupService.java | 38 ++++++++++++++++++- 6 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 src/main/java/project/flipnote/group/model/GroupPutRequest.java create mode 100644 src/main/java/project/flipnote/group/model/GroupPutResponse.java diff --git a/src/main/java/project/flipnote/group/controller/GroupController.java b/src/main/java/project/flipnote/group/controller/GroupController.java index efe9104a..ae951015 100644 --- a/src/main/java/project/flipnote/group/controller/GroupController.java +++ b/src/main/java/project/flipnote/group/controller/GroupController.java @@ -3,7 +3,9 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -13,6 +15,8 @@ import project.flipnote.common.security.dto.AuthPrinciple; import project.flipnote.group.model.GroupCreateRequest; import project.flipnote.group.model.GroupCreateResponse; +import project.flipnote.group.model.GroupPutRequest; +import project.flipnote.group.model.GroupPutResponse; import project.flipnote.group.service.GroupService; @RequiredArgsConstructor @@ -28,4 +32,13 @@ public ResponseEntity create( GroupCreateResponse res = groupService.create(authPrinciple, req); return ResponseEntity.status(HttpStatus.CREATED).body(res); } + + @PutMapping("/{groupId}") + public ResponseEntity changeGroup( + @AuthenticationPrincipal AuthPrinciple authPrinciple, + @Valid @RequestBody GroupPutRequest req, + @PathVariable("groupId") Long groupId) { + GroupPutResponse res = groupService.changeGroup(authPrinciple, req, groupId); + return ResponseEntity.ok(res); + } } diff --git a/src/main/java/project/flipnote/group/entity/Group.java b/src/main/java/project/flipnote/group/entity/Group.java index bf9551e7..9a021b87 100644 --- a/src/main/java/project/flipnote/group/entity/Group.java +++ b/src/main/java/project/flipnote/group/entity/Group.java @@ -8,6 +8,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; +import jakarta.validation.Valid; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; @@ -16,6 +17,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import project.flipnote.common.entity.BaseEntity; +import project.flipnote.group.model.GroupPutRequest; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -67,4 +69,14 @@ private Group( this.maxMember = maxMember; this.imageUrl = imageUrl; } + + public void changeGroup(GroupPutRequest req) { + this.name = req.name(); + this.category = req.category(); + this.description = req.description(); + this.applicationRequired = req.applicationRequired(); + this.publicVisible = req.publicVisible(); + this.maxMember = req.maxMember(); + this.imageUrl = req.image(); + } } diff --git a/src/main/java/project/flipnote/group/exception/GroupErrorCode.java b/src/main/java/project/flipnote/group/exception/GroupErrorCode.java index 3604efe4..56ea3f2e 100644 --- a/src/main/java/project/flipnote/group/exception/GroupErrorCode.java +++ b/src/main/java/project/flipnote/group/exception/GroupErrorCode.java @@ -11,7 +11,8 @@ @RequiredArgsConstructor public enum GroupErrorCode implements ErrorCode { GROUP_NOT_FOUND(HttpStatus.NOT_FOUND, "GROUP_002", "그룹이 존재하지 않습니다."), - INVALID_MAX_MEMBER(HttpStatus.BAD_REQUEST, "GROUP_001", "최대 인원 수는 1 이상 100 이하여야 합니다."); + INVALID_MAX_MEMBER(HttpStatus.BAD_REQUEST, "GROUP_001", "최대 인원 수는 1 이상 100 이하여야 합니다."), + INVALID_MEMBER_COUNT(HttpStatus.BAD_REQUEST, "GROUP_006", "그룹 내에 인원수보다 많게 수정해야합니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/project/flipnote/group/model/GroupPutRequest.java b/src/main/java/project/flipnote/group/model/GroupPutRequest.java new file mode 100644 index 00000000..6f3b614e --- /dev/null +++ b/src/main/java/project/flipnote/group/model/GroupPutRequest.java @@ -0,0 +1,31 @@ +package project.flipnote.group.model; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import project.flipnote.group.entity.Category; + +public record GroupPutRequest( + @NotBlank + @Size(max = 50) + String name, + + @NotNull + Category category, + + @NotBlank + @Size(max = 150) + String description, + + @NotNull + Boolean applicationRequired, + + @NotNull + Boolean publicVisible, + + @NotNull + Integer maxMember, + + String image +) { +} diff --git a/src/main/java/project/flipnote/group/model/GroupPutResponse.java b/src/main/java/project/flipnote/group/model/GroupPutResponse.java new file mode 100644 index 00000000..e160b9df --- /dev/null +++ b/src/main/java/project/flipnote/group/model/GroupPutResponse.java @@ -0,0 +1,9 @@ +package project.flipnote.group.model; + +public record GroupPutResponse( + Long groupId +) { + public static GroupPutResponse from(Long groupId) { + return new GroupPutResponse(groupId); + } +} diff --git a/src/main/java/project/flipnote/group/service/GroupService.java b/src/main/java/project/flipnote/group/service/GroupService.java index eba053c5..3a20f30c 100644 --- a/src/main/java/project/flipnote/group/service/GroupService.java +++ b/src/main/java/project/flipnote/group/service/GroupService.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import project.flipnote.common.exception.BizException; @@ -18,6 +19,8 @@ import project.flipnote.group.exception.GroupErrorCode; import project.flipnote.group.model.GroupCreateRequest; import project.flipnote.group.model.GroupCreateResponse; +import project.flipnote.group.model.GroupPutRequest; +import project.flipnote.group.model.GroupPutResponse; import project.flipnote.group.repository.GroupMemberRepository; import project.flipnote.group.repository.GroupPermissionRepository; import project.flipnote.group.repository.GroupRepository; @@ -46,6 +49,12 @@ public UserProfile findUser(AuthPrinciple authPrinciple) { ); } + public Group findGroup(Long groupId) { + return groupRepository.findById(groupId).orElseThrow( + () -> new BizException(GroupErrorCode.GROUP_NOT_FOUND) + ); + } + //그룹 생성 @Transactional public GroupCreateResponse create(AuthPrinciple authPrinciple, GroupCreateRequest req) { @@ -62,7 +71,7 @@ public GroupCreateResponse create(AuthPrinciple authPrinciple, GroupCreateReques //4. 그룹 회원 정보 생성 saveGroupOwner(group, userProfile); - //5. 그룹 내의 모든 권한 조회 + //5. 그룹 내의 모든 권한 생성 initializeGroupPermissions(group); return GroupCreateResponse.from(group.getId()); @@ -127,4 +136,31 @@ private void validateMaxMember(int maxMember) { } } + //유저수 검증 + private void validateUserCount(Group group, int maxMember) { + long count = groupMemberRepository.countByGroup_Id(group.getId()); + if (count > maxMember) { + throw new BizException(GroupErrorCode.INVALID_MEMBER_COUNT); + } + } + + //그룹 수정 + public GroupPutResponse changeGroup(AuthPrinciple authPrinciple, @Valid GroupPutRequest req, Long groupId) { + + //1. 유저 조회 + UserProfile userProfile = findUser(authPrinciple); + + //2. 인원수 검증 + validateMaxMember(req.maxMember()); + + //3. 그룹 가져오기 + Group group = findGroup(groupId); + + //4. 유저 수 보다 적게 할 경우 오류 + validateUserCount(group, req.maxMember()); + + group.changeGroup(req); + + return GroupPutResponse.from(group.getId()); + } } From a813f2e2e2a137bce0f4d640922c5b1d7b944ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Thu, 21 Aug 2025 17:51:59 +0900 Subject: [PATCH 2/4] =?UTF-8?q?Feat:=20=EA=B7=B8=EB=A3=B9=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group/controller/GroupController.java | 1 + .../group/model/GroupPutResponse.java | 37 ++++++- .../flipnote/group/service/GroupService.java | 25 +++-- .../group/service/GroupServiceTest.java | 103 ++++++++++++++++++ 4 files changed, 152 insertions(+), 14 deletions(-) diff --git a/src/main/java/project/flipnote/group/controller/GroupController.java b/src/main/java/project/flipnote/group/controller/GroupController.java index 1c253e6f..8b39ba96 100644 --- a/src/main/java/project/flipnote/group/controller/GroupController.java +++ b/src/main/java/project/flipnote/group/controller/GroupController.java @@ -38,6 +38,7 @@ public ResponseEntity create( return ResponseEntity.status(HttpStatus.CREATED).body(res); } + //그룹 수정 @PutMapping("/{groupId}") public ResponseEntity changeGroup( @AuthenticationPrincipal AuthPrinciple authPrinciple, diff --git a/src/main/java/project/flipnote/group/model/GroupPutResponse.java b/src/main/java/project/flipnote/group/model/GroupPutResponse.java index e160b9df..74d42986 100644 --- a/src/main/java/project/flipnote/group/model/GroupPutResponse.java +++ b/src/main/java/project/flipnote/group/model/GroupPutResponse.java @@ -1,9 +1,40 @@ package project.flipnote.group.model; +import java.time.LocalDateTime; + +import project.flipnote.group.entity.Category; +import project.flipnote.group.entity.Group; + public record GroupPutResponse( - Long groupId + String name, + + Category category, + + String description, + + Boolean applicationRequired, + + Boolean publicVisible, + + Integer maxMember, + + String imageUrl, + + LocalDateTime createdAt, + + LocalDateTime modifiedAt ) { - public static GroupPutResponse from(Long groupId) { - return new GroupPutResponse(groupId); + public static GroupPutResponse from(Group group) { + return new GroupPutResponse( + group.getName(), + group.getCategory(), + group.getDescription(), + group.getApplicationRequired(), + group.getPublicVisible(), + group.getMaxMember(), + group.getImageUrl(), + group.getCreatedAt(), + group.getModifiedAt() + ); } } diff --git a/src/main/java/project/flipnote/group/service/GroupService.java b/src/main/java/project/flipnote/group/service/GroupService.java index 7d31ba3d..6006d127 100644 --- a/src/main/java/project/flipnote/group/service/GroupService.java +++ b/src/main/java/project/flipnote/group/service/GroupService.java @@ -54,12 +54,6 @@ public UserProfile validateUser(AuthPrinciple authPrinciple) { ); } - public Group findGroup(Long groupId) { - return groupRepository.findById(groupId).orElseThrow( - () -> new BizException(GroupErrorCode.GROUP_NOT_FOUND) - ); - } - //그룹 생성 /* 그룹 내 유저 조회 @@ -176,8 +170,7 @@ private void validateMaxMember(int maxMember) { //유저수 검증 private void validateUserCount(Group group, int maxMember) { - long count = groupMemberRepository.countByGroup_Id(group.getId()); - if (count > maxMember) { + if (group.getMemberCount() > maxMember) { throw new BizException(GroupErrorCode.INVALID_MEMBER_COUNT); } } @@ -186,20 +179,30 @@ private void validateUserCount(Group group, int maxMember) { public GroupPutResponse changeGroup(AuthPrinciple authPrinciple, @Valid GroupPutRequest req, Long groupId) { //1. 유저 조회 - UserProfile userProfile = validateUser(authPrinciple); + UserProfile user = validateUser(authPrinciple); //2. 인원수 검증 validateMaxMember(req.maxMember()); //3. 그룹 가져오기 - Group group = findGroup(groupId); + Group group = validateGroup(groupId); + + //4. 그룹 내 유저 조회 + GroupMember groupMember = validateGroupInUser(user, groupId); + + //5. 유저 권환 조회 + if (!groupMember.getRole().equals(GroupMemberRole.OWNER)) { + throw new BizException(GroupErrorCode.USER_NOT_PERMISSION); + } //4. 유저 수 보다 적게 할 경우 오류 validateUserCount(group, req.maxMember()); group.changeGroup(req); - return GroupPutResponse.from(group.getId()); + groupRepository.save(group); + + return GroupPutResponse.from(group); } /* 그룹 내 오너를 제외한 인원이 존재하는 경우 체크 diff --git a/src/test/java/project/flipnote/group/service/GroupServiceTest.java b/src/test/java/project/flipnote/group/service/GroupServiceTest.java index a59116f6..032a4d6b 100644 --- a/src/test/java/project/flipnote/group/service/GroupServiceTest.java +++ b/src/test/java/project/flipnote/group/service/GroupServiceTest.java @@ -33,6 +33,8 @@ import project.flipnote.group.model.GroupCreateRequest; import project.flipnote.group.model.GroupCreateResponse; import project.flipnote.group.model.GroupDetailResponse; +import project.flipnote.group.model.GroupPutRequest; +import project.flipnote.group.model.GroupPutResponse; import project.flipnote.group.repository.GroupMemberRepository; import project.flipnote.group.repository.GroupPermissionRepository; import project.flipnote.group.repository.GroupRepository; @@ -306,4 +308,105 @@ void before() { assertEquals(GroupErrorCode.OTHER_USER_EXIST_IN_GROUP, exception.getErrorCode()); } + + @Test + public void 그룹_수정_성공() throws Exception { + //given + Group group = Group.builder() + .name("그룹1") + .category(Category.IT) + .description("설명1") + .publicVisible(true) + .applicationRequired(true) + .maxMember(100) + .imageUrl("www.~~~") + .build(); + ReflectionTestUtils.setField(group, "id", 1L); + + GroupMember groupMember = GroupMember.builder() + .group(group) + .role(GroupMemberRole.OWNER) + .user(userProfile) + .build(); + ReflectionTestUtils.setField(groupMember, "id", 1L); + + GroupPutRequest req = new GroupPutRequest("그룹1", Category.ENGLISH, "설명1", true, true, 100, "www.~~"); + + given(groupRepository.findByIdAndDeletedAtIsNull(group.getId())).willReturn(Optional.of(group)); + given(userProfileRepository.findByIdAndStatus(userProfile.getId(), UserStatus.ACTIVE)).willReturn(Optional.of(userProfile)); + given(groupMemberRepository.findByGroup_IdAndUser_Id(group.getId(),userProfile.getId())).willReturn(Optional.of(groupMember)); + //when + GroupPutResponse res = groupService.changeGroup(authPrinciple, req, group.getId()); + + //then + assertEquals(req.name(), group.getName()); + assertEquals(req.category(), group.getCategory()); + } + + @Test + public void 그룹_수정_실패_오너아닌경우() throws Exception { + //given + Group group = Group.builder() + .name("그룹1") + .category(Category.IT) + .description("설명1") + .publicVisible(true) + .applicationRequired(true) + .maxMember(100) + .imageUrl("www.~~~") + .build(); + ReflectionTestUtils.setField(group, "id", 1L); + + GroupMember groupMember = GroupMember.builder() + .group(group) + .role(GroupMemberRole.MEMBER) + .user(userProfile) + .build(); + ReflectionTestUtils.setField(groupMember, "id", 1L); + + GroupPutRequest req = new GroupPutRequest("그룹1", Category.ENGLISH, "설명1", true, true, 100, "www.~~"); + + given(groupRepository.findByIdAndDeletedAtIsNull(group.getId())).willReturn(Optional.of(group)); + given(userProfileRepository.findByIdAndStatus(userProfile.getId(), UserStatus.ACTIVE)).willReturn(Optional.of(userProfile)); + given(groupMemberRepository.findByGroup_IdAndUser_Id(group.getId(),userProfile.getId())).willReturn(Optional.of(groupMember)); + //when + BizException exception = + assertThrows(BizException.class, () -> groupService.changeGroup(authPrinciple, req, group.getId())); + + assertEquals(GroupErrorCode.USER_NOT_PERMISSION, exception.getErrorCode()); + } + + @Test + public void 그룹_수정_실패_유저수보다_적게한경우() throws Exception { + //given + Group group = Group.builder() + .name("그룹1") + .category(Category.IT) + .description("설명1") + .publicVisible(true) + .applicationRequired(true) + .maxMember(100) + .imageUrl("www.~~~") + .build(); + ReflectionTestUtils.setField(group, "id", 1L); + ReflectionTestUtils.setField(group, "memberCount", 100); + + GroupMember groupMember = GroupMember.builder() + .group(group) + .role(GroupMemberRole.OWNER) + .user(userProfile) + .build(); + ReflectionTestUtils.setField(groupMember, "id", 1L); + + GroupPutRequest req = new GroupPutRequest("그룹1", Category.ENGLISH, "설명1", true, true, 50, "www.~~"); + + given(groupRepository.findByIdAndDeletedAtIsNull(group.getId())).willReturn(Optional.of(group)); + given(userProfileRepository.findByIdAndStatus(userProfile.getId(), UserStatus.ACTIVE)).willReturn(Optional.of(userProfile)); + given(groupMemberRepository.findByGroup_IdAndUser_Id(group.getId(),userProfile.getId())).willReturn(Optional.of(groupMember)); + //when + BizException exception = + assertThrows(BizException.class, () -> groupService.changeGroup(authPrinciple, req, group.getId())); + + assertEquals(GroupErrorCode.INVALID_MEMBER_COUNT, exception.getErrorCode()); + } } From 923117de2b85300fb8c1b3d5c5cafd5e35918838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Thu, 21 Aug 2025 21:21:09 +0900 Subject: [PATCH 3/4] =?UTF-8?q?Feat:=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/project/flipnote/group/service/GroupService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/project/flipnote/group/service/GroupService.java b/src/main/java/project/flipnote/group/service/GroupService.java index 6006d127..7f57e7b4 100644 --- a/src/main/java/project/flipnote/group/service/GroupService.java +++ b/src/main/java/project/flipnote/group/service/GroupService.java @@ -176,6 +176,7 @@ private void validateUserCount(Group group, int maxMember) { } //그룹 수정 + @Transactional public GroupPutResponse changeGroup(AuthPrinciple authPrinciple, @Valid GroupPutRequest req, Long groupId) { //1. 유저 조회 From 269751ba2bb966736397b4c900013453e7f1f4c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Sat, 23 Aug 2025 00:18:05 +0900 Subject: [PATCH 4/4] =?UTF-8?q?Feat:=20=EA=B7=B8=EB=A3=B9=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project/flipnote/group/entity/Group.java | 6 + .../group/service/GroupPolicyService.java | 54 +++++++++ .../flipnote/group/service/GroupService.java | 14 +-- .../group/service/GroupPolicyServiceTest.java | 103 ++++++++++++++++++ .../group/service/GroupServiceTest.java | 20 +++- 5 files changed, 187 insertions(+), 10 deletions(-) create mode 100644 src/main/java/project/flipnote/group/service/GroupPolicyService.java create mode 100644 src/test/java/project/flipnote/group/service/GroupPolicyServiceTest.java diff --git a/src/main/java/project/flipnote/group/entity/Group.java b/src/main/java/project/flipnote/group/entity/Group.java index 88c6d9f7..cd000f8a 100644 --- a/src/main/java/project/flipnote/group/entity/Group.java +++ b/src/main/java/project/flipnote/group/entity/Group.java @@ -96,6 +96,12 @@ public void validateJoinable() { } } + public void validateMaxMemberUpdatable(int changeNumber) { + if (memberCount > changeNumber) { + throw new BizException(GroupErrorCode.INVALID_MEMBER_COUNT); + } + } + public void increaseMemberCount() { validateJoinable(); memberCount++; diff --git a/src/main/java/project/flipnote/group/service/GroupPolicyService.java b/src/main/java/project/flipnote/group/service/GroupPolicyService.java new file mode 100644 index 00000000..0d2864df --- /dev/null +++ b/src/main/java/project/flipnote/group/service/GroupPolicyService.java @@ -0,0 +1,54 @@ +package project.flipnote.group.service; + +import java.util.concurrent.TimeUnit; + +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import project.flipnote.common.exception.BizException; +import project.flipnote.common.exception.CommonErrorCode; +import project.flipnote.group.entity.Group; +import project.flipnote.group.exception.GroupErrorCode; +import project.flipnote.group.model.GroupPutRequest; +import project.flipnote.group.repository.GroupRepository; + +@Service +@RequiredArgsConstructor +public class GroupPolicyService { + private final GroupRepository groupRepository; + private final RedissonClient redissonClient; + + @Transactional + public Group changeGroup(Long groupId, GroupPutRequest req) { + String lockKey = "group_lock:" + groupId; + RLock lock = redissonClient.getLock(lockKey); + + boolean isLocked = false; + try { + isLocked = lock.tryLock(2, 3, TimeUnit.SECONDS); + if (!isLocked) { + throw new BizException(CommonErrorCode.SERVICE_TEMPORARILY_UNAVAILABLE); + } + + Group lockedGroup = groupRepository.findByIdForUpdate(groupId) + .orElseThrow(() -> new BizException(GroupErrorCode.GROUP_NOT_FOUND)); + + lockedGroup.validateMaxMemberUpdatable(req.maxMember()); + + lockedGroup.changeGroup(req); + + return lockedGroup; + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new BizException(CommonErrorCode.SERVICE_TEMPORARILY_UNAVAILABLE); + } finally { + if (isLocked && lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } +} diff --git a/src/main/java/project/flipnote/group/service/GroupService.java b/src/main/java/project/flipnote/group/service/GroupService.java index 7f57e7b4..deb58193 100644 --- a/src/main/java/project/flipnote/group/service/GroupService.java +++ b/src/main/java/project/flipnote/group/service/GroupService.java @@ -44,6 +44,7 @@ public class GroupService { private final GroupPermissionRepository groupPermissionRepository; private final GroupRolePermissionRepository groupRolePermissionRepository; private final UserProfileRepository userProfileRepository; + private final GroupPolicyService groupPolicyService; /* 유저 정보 조회 @@ -54,7 +55,6 @@ public UserProfile validateUser(AuthPrinciple authPrinciple) { ); } - //그룹 생성 /* 그룹 내 유저 조회 */ @@ -177,7 +177,7 @@ private void validateUserCount(Group group, int maxMember) { //그룹 수정 @Transactional - public GroupPutResponse changeGroup(AuthPrinciple authPrinciple, @Valid GroupPutRequest req, Long groupId) { + public GroupPutResponse changeGroup(AuthPrinciple authPrinciple, GroupPutRequest req, Long groupId) { //1. 유저 조회 UserProfile user = validateUser(authPrinciple); @@ -196,14 +196,10 @@ public GroupPutResponse changeGroup(AuthPrinciple authPrinciple, @Valid GroupPut throw new BizException(GroupErrorCode.USER_NOT_PERMISSION); } - //4. 유저 수 보다 적게 할 경우 오류 - validateUserCount(group, req.maxMember()); + //6. 그룹 수정 + Group changeGroup = groupPolicyService.changeGroup(groupId, req); - group.changeGroup(req); - - groupRepository.save(group); - - return GroupPutResponse.from(group); + return GroupPutResponse.from(changeGroup); } /* 그룹 내 오너를 제외한 인원이 존재하는 경우 체크 diff --git a/src/test/java/project/flipnote/group/service/GroupPolicyServiceTest.java b/src/test/java/project/flipnote/group/service/GroupPolicyServiceTest.java new file mode 100644 index 00000000..3a63863d --- /dev/null +++ b/src/test/java/project/flipnote/group/service/GroupPolicyServiceTest.java @@ -0,0 +1,103 @@ +package project.flipnote.group.service; + + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.util.Optional; + +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 org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.test.util.ReflectionTestUtils; + +import project.flipnote.common.exception.BizException; +import project.flipnote.group.entity.Category; +import project.flipnote.group.entity.Group; +import project.flipnote.group.exception.GroupErrorCode; +import project.flipnote.group.model.GroupPutRequest; +import project.flipnote.group.repository.GroupRepository; + +@ExtendWith(MockitoExtension.class) +class GroupPolicyServiceTest { + + @InjectMocks + GroupPolicyService groupPolicyService; + + @Mock + GroupRepository groupRepository; + + @Mock + RedissonClient redissonClient; + + @Mock + RLock rLock; + + @Test + void 실패_유저수보다_작게_변경() throws Exception { + Long groupId = 1L; + Group group = Group.builder() + .name("그룹1") + .category(Category.IT) + .description("설명1") + .publicVisible(true) + .applicationRequired(true) + .maxMember(100) + .imageUrl("www.~~~") + .build(); + + ReflectionTestUtils.setField(group, "id", 1L); + ReflectionTestUtils.setField(group, "memberCount", 100); + + GroupPutRequest req = new GroupPutRequest("그룹1", Category.ENGLISH, "설명1", true, true, 50, "www.~~"); + + given(redissonClient.getLock(anyString())).willReturn(rLock); + given(rLock.tryLock(anyLong(), anyLong(), any())).willReturn(true); + given(rLock.isHeldByCurrentThread()).willReturn(true); + given(groupRepository.findByIdForUpdate(groupId)).willReturn(Optional.of(group)); + + + //when & then + BizException exception = + assertThrows(BizException.class, () -> groupPolicyService.changeGroup(groupId, req)); + + assertEquals(GroupErrorCode.INVALID_MEMBER_COUNT, exception.getErrorCode()); + then(rLock).should().unlock(); + } + + @Test + void 그룹_수정_성공() throws Exception { + Long groupId = 1L; + Group group = Group.builder() + .name("그룹1") + .category(Category.IT) + .description("설명1") + .publicVisible(true) + .applicationRequired(true) + .maxMember(100) + .imageUrl("www.~~~") + .build(); + + ReflectionTestUtils.setField(group, "id", 1L); + ReflectionTestUtils.setField(group, "memberCount", 3); + + GroupPutRequest req = new GroupPutRequest("그룹1", Category.ENGLISH, "설명1", true, true, 50, "www.~~"); + + given(redissonClient.getLock(anyString())).willReturn(rLock); + given(rLock.tryLock(anyLong(), anyLong(), any())).willReturn(true); + given(rLock.isHeldByCurrentThread()).willReturn(true); + given(groupRepository.findByIdForUpdate(groupId)).willReturn(Optional.of(group)); + + + //when + Group changeGroup = groupPolicyService.changeGroup(groupId, req); + + assertEquals(req.name(), changeGroup.getName()); + assertEquals(req.category(), changeGroup.getCategory()); + + } +} diff --git a/src/test/java/project/flipnote/group/service/GroupServiceTest.java b/src/test/java/project/flipnote/group/service/GroupServiceTest.java index 032a4d6b..e4bd6996 100644 --- a/src/test/java/project/flipnote/group/service/GroupServiceTest.java +++ b/src/test/java/project/flipnote/group/service/GroupServiceTest.java @@ -14,6 +14,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.test.util.ReflectionTestUtils; @@ -69,6 +71,9 @@ class GroupServiceTest { @Mock GroupMemberRepository groupMemberRepository; + @Mock + GroupPolicyService groupPolicyService; + UserProfile userProfile; AuthPrinciple authPrinciple; @@ -156,7 +161,7 @@ void before() { .role(GroupMemberRole.MEMBER) .build(); - given(groupRepository.findByIdAndDeletedAtIsNull(any())).willReturn(Optional.ofNullable(group)); + given(groupRepository.findByIdAndDeletedAtIsNull(any())).willReturn(Optional.of(group)); given(groupMemberRepository.findByGroup_IdAndUser_Id(any(), any())).willReturn(Optional.of(groupMember)); // 사용자 검증 로직 given(userProfileRepository.findByIdAndStatus(userProfile.getId(), UserStatus.ACTIVE)) @@ -335,6 +340,15 @@ void before() { given(groupRepository.findByIdAndDeletedAtIsNull(group.getId())).willReturn(Optional.of(group)); given(userProfileRepository.findByIdAndStatus(userProfile.getId(), UserStatus.ACTIVE)).willReturn(Optional.of(userProfile)); given(groupMemberRepository.findByGroup_IdAndUser_Id(group.getId(),userProfile.getId())).willReturn(Optional.of(groupMember)); + + willAnswer(inv -> { + Long idArg = inv.getArgument(0, Long.class); + GroupPutRequest reqArg = inv.getArgument(1, GroupPutRequest.class); + // 실제 서비스 로직처럼 그룹 변경을 흉내냄 + group.changeGroup(reqArg); + return group; // 변경된 그룹 반환 + }).given(groupPolicyService).changeGroup(group.getId(), req); + //when GroupPutResponse res = groupService.changeGroup(authPrinciple, req, group.getId()); @@ -403,6 +417,10 @@ void before() { given(groupRepository.findByIdAndDeletedAtIsNull(group.getId())).willReturn(Optional.of(group)); given(userProfileRepository.findByIdAndStatus(userProfile.getId(), UserStatus.ACTIVE)).willReturn(Optional.of(userProfile)); given(groupMemberRepository.findByGroup_IdAndUser_Id(group.getId(),userProfile.getId())).willReturn(Optional.of(groupMember)); + + willThrow(new BizException(GroupErrorCode.INVALID_MEMBER_COUNT)) + .given(groupPolicyService).changeGroup(group.getId(), req); + //when BizException exception = assertThrows(BizException.class, () -> groupService.changeGroup(authPrinciple, req, group.getId()));