Skip to content

Commit

Permalink
fix: handle change propagation
Browse files Browse the repository at this point in the history
  • Loading branch information
psychology50 committed Feb 7, 2025
1 parent 0cf84d2 commit dfc33a5
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,9 @@ public void successToRetrieveChatRoomWithParticipantsAndRecentMessages() {
@DisplayName("일반 회원이 채팅방 참여자 정보와 최근 메시지를 조회하면 관리자 정보도 함께 조회된다")
public void memberSuccessToRetrieveChatRoomWithParticipantsIncludingAdmin() {
// given
ChatMember myInfo = createChatMember(userId, UserFixture.GENERAL_USER.toUser(), chatRoom, ChatMemberRole.MEMBER);
ChatMemberResult.Detail adminDetail = new ChatMemberResult.Detail(2L, "Admin", ChatMemberRole.ADMIN, true, 2L, LocalDateTime.now(), myInfo.getUser().getProfileImageUrl());
User user = UserFixture.GENERAL_USER.toUser();
ChatMember myInfo = createChatMember(userId, user, chatRoom, ChatMemberRole.MEMBER);
ChatMemberResult.Detail adminDetail = new ChatMemberResult.Detail(2L, "Admin", ChatMemberRole.ADMIN, true, 2L, LocalDateTime.now(), user.getProfileImageUrl());
List<ChatMessage> recentMessages = createRecentMessages();
List<ChatMemberResult.Detail> recentParticipants = createRecentParticipantDetails();
List<ChatMemberResult.Summary> otherParticipants = createOtherParticipantSummaries();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

public interface ChatMemberRepository extends ExtendedRepository<ChatMember, Long>, CustomChatMemberRepository {
@Transactional(readOnly = true)
Set<ChatMember> findByChatRoom_IdAndUser_Id(Long chatRoomId, Long userId);
Set<ChatMember> findByChatRoom_IdAndUserId(Long chatRoomId, Long userId);

@Transactional(readOnly = true)
Optional<ChatMember> findByChatRoom_IdAndRole(Long chatRoomId, ChatMemberRole role);
Expand All @@ -30,19 +30,19 @@ public interface ChatMemberRepository extends ExtendedRepository<ChatMember, Lon
Optional<ChatMember> findByChatMember_Id(Long chatMemberId);

@Transactional(readOnly = true)
@Query("SELECT cm FROM ChatMember cm WHERE cm.chatRoom.id = :chatRoomId AND cm.user.id = :userId AND cm.deletedAt IS NULL")
@Query("SELECT cm FROM ChatMember cm WHERE cm.chatRoom.id = :chatRoomId AND cm.userId = :userId AND cm.deletedAt IS NULL")
Optional<ChatMember> findActiveChatMember(Long chatRoomId, Long userId);

@Transactional(readOnly = true)
@Query("SELECT COUNT(*) FROM ChatMember cm WHERE cm.chatRoom.id = :chatRoomId AND cm.deletedAt IS NULL")
long countByChatRoomIdAndActive(Long chatRoomId);

@Transactional(readOnly = true)
@Query("SELECT cm.chatRoom.id FROM ChatMember cm WHERE cm.user.id = :userId AND cm.deletedAt IS NULL")
@Query("SELECT cm.chatRoom.id FROM ChatMember cm WHERE cm.userId = :userId AND cm.deletedAt IS NULL")
Set<Long> findChatRoomIdsByUserId(Long userId);

@Transactional(readOnly = true)
@Query("SELECT cm.user.id FROM ChatMember cm WHERE cm.chatRoom.id = :chatRoomId AND cm.deletedAt IS NULL")
@Query("SELECT cm.userId FROM ChatMember cm WHERE cm.chatRoom.id = :chatRoomId AND cm.deletedAt IS NULL")
Set<Long> findUserIdsByChatRoomId(Long chatRoomId);

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import kr.co.pennyway.domain.domains.member.domain.QChatMember;
import kr.co.pennyway.domain.domains.member.dto.ChatMemberResult;
import kr.co.pennyway.domain.domains.member.type.ChatMemberRole;
import kr.co.pennyway.domain.domains.user.domain.QUser;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

Expand All @@ -17,13 +18,14 @@ public class CustomChatMemberRepositoryImpl implements CustomChatMemberRepositor
private final JPAQueryFactory queryFactory;

private final QChatMember chatMember = QChatMember.chatMember;
private final QUser user = QUser.user;

@Override
public boolean existsByChatRoomIdAndUserId(Long chatRoomId, Long userId) {
return queryFactory.select(ConstantImpl.create(1))
.from(chatMember)
.where(chatMember.chatRoom.id.eq(chatRoomId)
.and(chatMember.user.id.eq(userId))
.and(chatMember.userId.eq(userId))
.and(chatMember.deletedAt.isNull()))
.fetchFirst() != null;
}
Expand All @@ -32,7 +34,7 @@ public boolean existsByChatRoomIdAndUserId(Long chatRoomId, Long userId) {
public boolean existsOwnershipChatRoomByUserId(Long userId) {
return queryFactory.select(ConstantImpl.create(1))
.from(chatMember)
.where(chatMember.user.id.eq(userId)
.where(chatMember.userId.eq(userId)
.and(chatMember.role.eq(ChatMemberRole.ADMIN))
.and(chatMember.deletedAt.isNull()))
.fetchFirst() != null;
Expand All @@ -43,7 +45,7 @@ public boolean existsByChatRoomIdAndUserIdAndId(Long chatRoomId, Long userId, Lo
return queryFactory.select(ConstantImpl.create(1))
.from(chatMember)
.where(chatMember.chatRoom.id.eq(chatRoomId)
.and(chatMember.user.id.eq(userId))
.and(chatMember.userId.eq(userId))
.and(chatMember.id.eq(chatMemberId))
.and(chatMember.deletedAt.isNull()))
.fetchFirst() != null;
Expand All @@ -56,15 +58,16 @@ public Optional<ChatMemberResult.Detail> findAdminByChatRoomId(Long chatRoomId)
Projections.constructor(
ChatMemberResult.Detail.class,
chatMember.id,
chatMember.user.name,
user.name,
chatMember.role,
chatMember.notifyEnabled,
chatMember.user.id,
user.id,
chatMember.createdAt,
chatMember.user.profileImageUrl
user.profileImageUrl
)
)
.from(chatMember)
.innerJoin(user).on(chatMember.userId.eq(user.id))
.where(chatMember.chatRoom.id.eq(chatRoomId)
.and(chatMember.role.eq(ChatMemberRole.ADMIN))
.and(chatMember.deletedAt.isNull()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Predicate;
import kr.co.pennyway.common.annotation.DomainService;
import kr.co.pennyway.domain.common.repository.QueryHandler;
import kr.co.pennyway.domain.domains.chatroom.domain.ChatRoom;
import kr.co.pennyway.domain.domains.member.domain.ChatMember;
import kr.co.pennyway.domain.domains.member.domain.QChatMember;
Expand All @@ -11,6 +12,7 @@
import kr.co.pennyway.domain.domains.member.exception.ChatMemberErrorException;
import kr.co.pennyway.domain.domains.member.repository.ChatMemberRepository;
import kr.co.pennyway.domain.domains.member.type.ChatMemberRole;
import kr.co.pennyway.domain.domains.user.domain.QUser;
import kr.co.pennyway.domain.domains.user.domain.User;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -24,6 +26,7 @@
public class ChatMemberRdbService {
private final ChatMemberRepository chatMemberRepository;

private final QUser qUser = QUser.user;
private final QChatMember qChatMember = QChatMember.chatMember;

@Transactional
Expand All @@ -40,7 +43,7 @@ public ChatMember createAdmin(User user, ChatRoom chatRoom) {

@Transactional
public ChatMember createMember(User user, ChatRoom chatRoom) {
Set<ChatMember> chatMembers = chatMemberRepository.findByChatRoom_IdAndUser_Id(chatRoom.getId(), user.getId());
Set<ChatMember> chatMembers = chatMemberRepository.findByChatRoom_IdAndUserId(chatRoom.getId(), user.getId());

if (chatMembers.stream().anyMatch(ChatMember::isActive)) {
log.warn("사용자는 이미 채팅방에 가입되어 있습니다. chatRoomId: {}, userId: {}", chatRoom.getId(), user.getId());
Expand Down Expand Up @@ -79,51 +82,57 @@ public Set<ChatMember> readChatMembersByChatRoomId(Long chatRoomId) {

@Transactional(readOnly = true)
public List<ChatMemberResult.Detail> readChatMembersByIdIn(Long chatRoomId, Set<Long> chatMemberIds) {
QueryHandler queryHandler = query -> query.innerJoin(qUser).on(qChatMember.userId.eq(qUser.id));

Predicate predicate = qChatMember.chatRoom.id.eq(chatRoomId)
.and(qChatMember.id.in(chatMemberIds))
.and(qChatMember.deletedAt.isNull());

Map<String, Expression<?>> bindings = new LinkedHashMap<>();
bindings.put("id", qChatMember.id);
bindings.put("name", qChatMember.user.name);
bindings.put("name", qUser.name);
bindings.put("role", qChatMember.role);
bindings.put("notification", qChatMember.notifyEnabled);
bindings.put("userId", qChatMember.user.id);
bindings.put("userId", qUser.id);
bindings.put("createdAt", qChatMember.createdAt);
bindings.put("profileImageUrl", qChatMember.user.profileImageUrl);
bindings.put("profileImageUrl", qUser.profileImageUrl);

return chatMemberRepository.selectList(predicate, ChatMemberResult.Detail.class, bindings, null, null);
return chatMemberRepository.selectList(predicate, ChatMemberResult.Detail.class, bindings, queryHandler, null);
}

@Transactional(readOnly = true)
public List<ChatMemberResult.Detail> readChatMembersByUserIdIn(Long chatRoomId, Set<Long> userIds) {
QueryHandler queryHandler = query -> query.innerJoin(qUser).on(qChatMember.userId.eq(qUser.id));

Predicate predicate = qChatMember.chatRoom.id.eq(chatRoomId)
.and(qChatMember.user.id.in(userIds))
.and(qUser.id.in(userIds))
.and(qChatMember.deletedAt.isNull());

Map<String, Expression<?>> bindings = new LinkedHashMap<>();
bindings.put("id", qChatMember.id);
bindings.put("name", qChatMember.user.name);
bindings.put("id", qUser.id);
bindings.put("name", qUser.name);
bindings.put("role", qChatMember.role);
bindings.put("notification", qChatMember.notifyEnabled);
bindings.put("userId", qChatMember.user.id);
bindings.put("userId", qUser.id);
bindings.put("createdAt", qChatMember.createdAt);
bindings.put("profileImageUrl", qChatMember.user.profileImageUrl);
bindings.put("profileImageUrl", qUser.profileImageUrl);

return chatMemberRepository.selectList(predicate, ChatMemberResult.Detail.class, bindings, null, null);
return chatMemberRepository.selectList(predicate, ChatMemberResult.Detail.class, bindings, queryHandler, null);
}

@Transactional(readOnly = true)
public List<ChatMemberResult.Summary> readChatMemberIdsByUserIdNotIn(Long chatRoomId, Set<Long> userIds) {
QueryHandler queryHandler = query -> query.innerJoin(qUser).on(qChatMember.userId.eq(qUser.id));

Predicate predicate = qChatMember.chatRoom.id.eq(chatRoomId)
.and(qChatMember.user.id.notIn(userIds))
.and(qUser.id.notIn(userIds))
.and(qChatMember.deletedAt.isNull());

Map<String, Expression<?>> bindings = new LinkedHashMap<>();
bindings.put("id", qChatMember.id);
bindings.put("name", qChatMember.user.name);
bindings.put("name", qUser.name);

return chatMemberRepository.selectList(predicate, ChatMemberResult.Summary.class, bindings, null, null);
return chatMemberRepository.selectList(predicate, ChatMemberResult.Summary.class, bindings, queryHandler, null);
}

@Transactional(readOnly = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ void setUp() {
void createMemberWhenAlreadyExist() {
// given
ChatMember chatMember = ChatMember.of(user, chatRoom, ChatMemberRole.MEMBER);
given(chatMemberRepository.findByChatRoom_IdAndUser_Id(chatRoom.getId(), user.getId())).willReturn(Set.of(chatMember));
given(chatMemberRepository.findByChatRoom_IdAndUserId(chatRoom.getId(), user.getId())).willReturn(Set.of(chatMember));

// when
ChatMemberErrorException exception = assertThrows(ChatMemberErrorException.class, () -> chatMemberService.createMember(user, chatRoom));
Expand All @@ -65,7 +65,7 @@ void createMemberWhenBanned() {
// given
ChatMember chatMember = ChatMember.of(user, chatRoom, ChatMemberRole.MEMBER);
chatMember.ban();
given(chatMemberRepository.findByChatRoom_IdAndUser_Id(chatRoom.getId(), user.getId())).willReturn(Set.of(chatMember));
given(chatMemberRepository.findByChatRoom_IdAndUserId(chatRoom.getId(), user.getId())).willReturn(Set.of(chatMember));

// when
ChatMemberErrorException exception = assertThrows(ChatMemberErrorException.class, () -> chatMemberService.createMember(user, chatRoom));
Expand All @@ -79,7 +79,7 @@ void createMemberWhenBanned() {
void createMemberWhenNotExist() {

// given
given(chatMemberRepository.findByChatRoom_IdAndUser_Id(chatRoom.getId(), user.getId())).willReturn(Set.of());
given(chatMemberRepository.findByChatRoom_IdAndUserId(chatRoom.getId(), user.getId())).willReturn(Set.of());

ChatMember chatMember = ChatMember.of(user, chatRoom, ChatMemberRole.MEMBER);
given(chatMemberRepository.save(any(ChatMember.class))).willReturn(chatMember);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;

import java.util.Set;
import java.util.Collection;

@Slf4j
public class ChatMemberJoinOperation {
private static final long MAX_MEMBER_COUNT = 300;

private final User user;
private final ChatRoom chatRoom;
private final Set<ChatMember> chatMembers;
private final Collection<ChatMember> chatMembers;

public ChatMemberJoinOperation(@NonNull User user, @NonNull ChatRoom chatRoom, @NonNull Set<ChatMember> chatMembers) {
public ChatMemberJoinOperation(@NonNull User user, @NonNull ChatRoom chatRoom, @NonNull Collection<ChatMember> chatMembers) {
this.user = user;
this.chatRoom = chatRoom;
this.chatMembers = chatMembers;
Expand Down Expand Up @@ -65,7 +65,8 @@ public ChatMember execute(Integer password) {
}

private boolean isAlreadyJoined(ChatMember chatMember) {
return chatMembers.contains(chatMember);
return chatMembers.stream()
.anyMatch(member -> member.getUserId().equals(chatMember.getUserId()));
}

private boolean isFullRoom() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,60 @@

import kr.co.pennyway.domain.context.common.fixture.ChatRoomFixture;
import kr.co.pennyway.domain.context.common.fixture.UserFixture;
import kr.co.pennyway.domain.domains.chatroom.domain.ChatRoom;
import kr.co.pennyway.domain.domains.chatroom.exception.ChatRoomErrorCode;
import kr.co.pennyway.domain.domains.chatroom.exception.ChatRoomErrorException;
import kr.co.pennyway.domain.domains.member.domain.ChatMember;
import kr.co.pennyway.domain.domains.member.exception.ChatMemberErrorCode;
import kr.co.pennyway.domain.domains.member.exception.ChatMemberErrorException;
import kr.co.pennyway.domain.domains.member.type.ChatMemberRole;
import kr.co.pennyway.domain.domains.user.domain.User;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class ChatMemberJoinOperationTest {
private static final long MAX_MEMBER_COUNT = 300L;

@Test
@DisplayName("이미 채팅방에 참여한 사용자가 다시 참여할 때 가입에 실패한다")
void failWhenAlreadyJoined() {
// given
var user = createUser(1L);
var chatRoom = ChatRoomFixture.PUBLIC_CHAT_ROOM.toEntity();
var chatMembers = List.of(ChatMember.of(user, chatRoom, ChatMemberRole.MEMBER));

var operation = new ChatMemberJoinOperation(user, chatRoom, chatMembers);

// when & then
assertThatThrownBy(() -> operation.execute(chatRoom.getPassword()))
.isInstanceOf(ChatMemberErrorException.class)
.hasFieldOrPropertyWithValue("baseErrorCode", ChatMemberErrorCode.ALREADY_JOINED);
}

@Test
@DisplayName("채팅방이 가득 찼을 때 (정원 300명) 가입에 실패한다")
void failWhenChatRoomIsFull() {
// given
var user = createUser(1L);
var chatRoom = ChatRoomFixture.PUBLIC_CHAT_ROOM.toEntity();
var operation = new ChatMemberJoinOperation(user, chatRoom, MAX_MEMBER_COUNT);
var chatMembers = IntStream.range(0, 300)
.mapToObj(i -> createChatMember(i + 2L, chatRoom)) // userId 2~301
.collect(Collectors.toSet());

var operation = new ChatMemberJoinOperation(user, chatRoom, chatMembers);

// when & then
assertThatThrownBy(() -> operation.execute(null))
assertThatThrownBy(() -> operation.execute(chatRoom.getPassword()))
.isInstanceOf(ChatRoomErrorException.class)
.hasFieldOrPropertyWithValue("baseErrorCode", ChatRoomErrorCode.FULL_CHAT_ROOM);
}
Expand All @@ -38,7 +66,9 @@ void failWhenPasswordIsNotMatch() {
// given
var user = createUser(1L);
var chatRoom = ChatRoomFixture.PRIVATE_CHAT_ROOM.toEntity();
var operation = new ChatMemberJoinOperation(user, chatRoom, 0L);
var chatMembers = new HashSet<ChatMember>();

var operation = new ChatMemberJoinOperation(user, chatRoom, chatMembers);

// when & then
assertThatThrownBy(() -> operation.execute(235676))
Expand All @@ -52,14 +82,16 @@ void successWhenChatRoomIsNotFullAndPublic() {
// given
var user = createUser(1L);
var chatRoom = ChatRoomFixture.PUBLIC_CHAT_ROOM.toEntity();
var operation = new ChatMemberJoinOperation(user, chatRoom, 0L);
var chatMembers = new HashSet<ChatMember>();

var operation = new ChatMemberJoinOperation(user, chatRoom, chatMembers);

// when
ChatMember result = operation.execute(null);
ChatMember result = operation.execute(chatRoom.getPassword());

// then
assertAll(
() -> assertEquals(user, result.getUser()),
() -> assertEquals(user.getId(), result.getUserId()),
() -> assertEquals(chatRoom, result.getChatRoom()),
() -> assertEquals(ChatMemberRole.MEMBER, result.getRole())
);
Expand All @@ -71,14 +103,16 @@ void successWhenChatRoomIsNotFullAndPrivate() {
// given
var user = createUser(1L);
var chatRoom = ChatRoomFixture.PRIVATE_CHAT_ROOM.toEntity();
var operation = new ChatMemberJoinOperation(user, chatRoom, 0L);
var chatMembers = new HashSet<ChatMember>();

var operation = new ChatMemberJoinOperation(user, chatRoom, chatMembers);

// when
ChatMember result = operation.execute(chatRoom.getPassword());

// then
assertAll(
() -> assertEquals(user, result.getUser()),
() -> assertEquals(user.getId(), result.getUserId()),
() -> assertEquals(chatRoom, result.getChatRoom()),
() -> assertEquals(ChatMemberRole.MEMBER, result.getRole())
);
Expand All @@ -89,4 +123,8 @@ private User createUser(Long userId) {
ReflectionTestUtils.setField(user, "id", userId);
return user;
}

private ChatMember createChatMember(Long userId, ChatRoom chatRoom) {
return ChatMember.of(createUser(userId), chatRoom, ChatMemberRole.MEMBER);
}
}
Loading

0 comments on commit dfc33a5

Please sign in to comment.