Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions apps/chat/services/message_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import IntegrityError

from apps.chat.models import ChatMessage
from apps.study_groups.models import GroupMember, StudyGroup
from apps.users.models import User


class MessageService:
# 메시지 생성 및 단건 조회 기능 제공

@staticmethod
def validate_member(study_group: StudyGroup, user: User) -> None:
# 해당 사용자가 그룹 구성원이 맞는지 검증
is_member = GroupMember.objects.filter(
study_group_id=study_group.id,
user_id=user.id, # mypy 오류로 user 대신 user.id를 넘겼습니다.
).exists()

if not is_member:
raise PermissionDenied("그룹 멤버만 메시지를 작성할 수 있습니다.")

@staticmethod
def create_message(
study_group: StudyGroup,
user: User,
content: str,
) -> ChatMessage:

if not content or not content.strip():
raise ValidationError("메시지 내용은 비어 있을 수 없습니다.")

MessageService.validate_member(study_group, user)

try:
return ChatMessage.objects.create(
study_group=study_group,
sender=user,
content=content.strip(),
)
except IntegrityError:
raise ValidationError("메시지를 생성할 수 없습니다.")

@staticmethod
def get_message(message_id: int) -> ChatMessage:
msg = ChatMessage.objects.select_related("sender").filter(id=message_id).first()

if msg is None:
raise ValidationError("메시지를 찾을 수 없습니다.")

return msg
151 changes: 151 additions & 0 deletions apps/chat/tests/test_chatroom_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import time

from django.core.exceptions import PermissionDenied
from django.test import TestCase

from apps.chat.models.chat_message import ChatMessage
from apps.chat.models.last_read_message import LastReadMessage
from apps.chat.services.chatroom_service import ChatRoomService
from apps.study_groups.models import GroupMember, StudyGroup
from apps.users.models.users import User


class TestChatRoomService(TestCase):

def setUp(self) -> None:
# 사용자
self.user = User.objects.create(
email="user@example.com",
name="김오즈",
nickname="테스트",
phone_number="01012345678",
gender="M",
birthday="1999-01-01",
)

self.other_user = User.objects.create(
email="hayeong@example.com",
name="송하영",
nickname="ha0",
phone_number="01012341234",
gender="F",
birthday="1997-09-29",
)

# 스터디 그룹
self.group = StudyGroup.objects.create(
name="Django Study",
introduction="스터디 소개",
max_headcount=5,
start_at="2025-12-01T00:00:00Z",
end_at="2025-12-30T00:00:00Z",
)

# user만 그룹 멤버로 등록
GroupMember.objects.create(
study_group_id=self.group,
user_id=self.user,
is_leader=True,
)

# 테스트용 메시지 생성
self.msg1 = ChatMessage.objects.create(
study_group=self.group,
sender=self.user,
content="첫 번째 메시지",
)
time.sleep(0.001) # 생성 시간 차이를 내려고 넣었습니다

self.msg2 = ChatMessage.objects.create(
study_group=self.group,
sender=self.user,
content="두 번째 메시지",
)

# 멤버 검증 테스트
def test_validate_member_success(self) -> None:
# 그룹에 속한 사용자는 검증 통과
ChatRoomService.validate_member(self.group, self.user)

def test_validate_member_fail(self) -> None:
# 그룹에 속하지 않은 사용자는 PermissionDenied
with self.assertRaises(PermissionDenied):
ChatRoomService.validate_member(self.group, self.other_user)

def test_get_messages(self) -> None:
# 해당 그룹의 메시지 목록을 시간순으로 반환
messages = ChatRoomService.get_messages(self.group, self.user)
self.assertEqual(messages.count(), 2)

def test_get_room_info(self) -> None:
info = ChatRoomService.get_room_info(self.group, self.user)
self.assertEqual(info["group_id"], self.group.id)
self.assertEqual(len(info["members"]), 1)
self.assertEqual(info["members"][0]["nickname"], self.user.nickname)

def test_mark_all_read(self) -> None:
ChatRoomService.mark_all_read(self.group, self.user)

record = LastReadMessage.objects.get(
study_group=self.group,
user=self.user,
)
self.assertEqual(record.message.id, self.msg2.id)

def test_mark_all_read_no_messages(self) -> None:
# 메시지가 전혀 없는 새로운 그룹 생성
empty_group = StudyGroup.objects.create(
name="Empty Group",
introduction="메시지 없음",
max_headcount=5,
start_at="2025-12-01T00:00:00Z",
end_at="2025-12-30T00:00:00Z",
)

GroupMember.objects.create(
study_group_id=empty_group,
user_id=self.user,
is_leader=True,
)

# 실행
ChatRoomService.mark_all_read(empty_group, self.user)

# 메시지가 없으면 last_read 레코드도 x
record = LastReadMessage.objects.filter(
study_group=empty_group,
user=self.user,
).first()

self.assertIsNone(record)

def test_get_chatrooms_basic(self) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그룹 id가 정확히 반환되는지랑 그룹에 last_read가 없을때인 상황에 unread_count가 전체 메시지 개수인지도
있으면 좋을거 같아요

# 채팅방 목록 조회 기본 테스트
result = ChatRoomService.get_chatrooms(self.user)

self.assertEqual(len(result), 1)

room = result[0]

# 그룹 id 검증
self.assertEqual(room["group_id"], self.group.id)

# 그룹 이름 검증
self.assertEqual(room["group_name"], self.group.name)

# last_read x -> unread count = 전체 메시지 개수
total_messages = ChatMessage.objects.filter(study_group=self.group).count()

self.assertEqual(room["unread_count"], total_messages)

def test_get_chatrooms_with_last_read(self) -> None:
LastReadMessage.objects.create(
study_group=self.group,
user=self.user,
message=self.msg1,
)

result = ChatRoomService.get_chatrooms(self.user)

self.assertEqual(len(result), 1)
self.assertEqual(result[0]["unread_count"], 1)
105 changes: 105 additions & 0 deletions apps/chat/tests/test_last_read_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from django.core.exceptions import PermissionDenied
from django.test import TestCase

from apps.chat.models.chat_message import ChatMessage
from apps.chat.models.last_read_message import LastReadMessage
from apps.chat.services.last_read_service import LastReadService
from apps.study_groups.models import GroupMember, StudyGroup
from apps.users.models.users import User


class TestLastReadService(TestCase):

def setUp(self) -> None:
# 사용자
self.user = User.objects.create(
email="test@example.com",
name="김오즈",
nickname="테스터",
phone_number="01012345678",
gender="M",
birthday="1999-01-01",
)

self.other_user = User.objects.create(
email="hayeong@example.com",
name="송하영",
nickname="ha0",
phone_number="01012341234",
gender="F",
birthday="1997-09-29",
)

self.group = StudyGroup.objects.create(
name="Django Study",
introduction="스터디 소개",
max_headcount=5,
start_at="2025-12-01T00:00:00Z",
end_at="2025-12-30T00:00:00Z",
)

GroupMember.objects.create(
study_group_id=self.group,
user_id=self.user,
is_leader=True,
)

self.message1 = ChatMessage.objects.create(
study_group=self.group,
sender=self.user,
content="메시지 1",
)

self.message2 = ChatMessage.objects.create(
study_group=self.group,
sender=self.user,
content="메시지 2",
)

def test_update_last_read_create(self) -> None:
# 처음 읽을 경우 LastReadMessage 생성
LastReadService.update_last_read(
study_group=self.group,
user=self.user,
message=self.message1,
)

record = LastReadMessage.objects.filter(
study_group=self.group,
user=self.user,
).first()

self.assertIsNotNone(record)
assert record is not None
self.assertEqual(record.message.id, self.message1.id)

def test_update_last_read_update(self) -> None:
# 이미 읽은 과거가 있으면 제일 최신 메시지로 업데이트

LastReadMessage.objects.create(
study_group=self.group,
user=self.user,
message=self.message1,
)

LastReadService.update_last_read(
study_group=self.group,
user=self.user,
message=self.message2,
)

updated = LastReadMessage.objects.get(
study_group=self.group,
user=self.user,
)

self.assertEqual(updated.message.id, self.message2.id)

def test_update_last_read_fail_other_user(self) -> None:
# 그룹 구성원이 아니라면 PermissionDenied
with self.assertRaises(PermissionDenied):
LastReadService.update_last_read(
study_group=self.group,
user=self.other_user,
message=self.message1,
)
89 changes: 89 additions & 0 deletions apps/chat/tests/test_message_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from django.core.exceptions import PermissionDenied, ValidationError
from django.test import TestCase

from apps.chat.models.chat_message import ChatMessage
from apps.chat.services.message_service import MessageService
from apps.study_groups.models import GroupMember, StudyGroup
from apps.users.models.users import User


class TestMessageService(TestCase):

def setUp(self) -> None:
# 사용자 생성
self.user = User.objects.create(
email="test@example.com",
name="김오즈",
nickname="테스터",
phone_number="01012345678",
gender="M",
birthday="1999-01-01",
)

self.other_user = User.objects.create(
email="hayeong@example.com",
name="송하영",
nickname="ha0",
phone_number="01012341234",
gender="F",
birthday="1997-09-29",
)

# 스터디 그룹 생성
self.group = StudyGroup.objects.create(
name="Django Study",
introduction="스터디 소개",
max_headcount=5,
start_at="2025-12-01T00:00:00Z",
end_at="2025-12-30T00:00:00Z",
)

# 그룹 멤버 등록
GroupMember.objects.create(
study_group_id=self.group,
user_id=self.user,
is_leader=True,
)

# 메시지 생성 테스트
def test_create_message_success(self) -> None:
"""그룹 멤버는 정상적으로 메시지 생성 가능"""

msg = MessageService.create_message(study_group=self.group, user=self.user, content="안녕하세요!")

self.assertIsInstance(msg, ChatMessage)
self.assertEqual(msg.content, "안녕하세요!")
self.assertEqual(msg.sender_id, self.user.id)

def test_create_message_fail_for_non_member(self) -> None:
# 그룹 멤버가 아니면 PermissionDenied 발생

with self.assertRaises(PermissionDenied):
MessageService.create_message(study_group=self.group, user=self.other_user, content="멤버가 아닙니다")

def test_create_message_fail_empty_content(self) -> None:
# 빈 메시지 내용이면 ValidationError 발생

with self.assertRaises(ValidationError):
MessageService.create_message(study_group=self.group, user=self.user, content=" ")

# 메시지 단일 조회 테스트
def test_get_message_success(self) -> None:
"""단일 메시지 조회 성공"""

msg = ChatMessage.objects.create(
study_group=self.group,
sender=self.user,
content="조회 테스트",
)

found = MessageService.get_message(msg.id)

self.assertEqual(found.id, msg.id)
self.assertEqual(found.content, "조회 테스트")

def test_get_message_not_found(self) -> None:
# 없는 메시지를 조회하면 ValidationError 발생

with self.assertRaises(ValidationError):
MessageService.get_message(9999)
Loading