From 1570cc7f535f2eaf16f7b8a0be2002f4fad58935 Mon Sep 17 00:00:00 2001 From: s4ngmin-9 Date: Wed, 3 Dec 2025 10:25:44 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20feat:=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EC=83=9D=EC=84=B1/=EC=A1=B0=ED=9A=8C=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=203=EC=A2=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MessageService 기능 구현 및 서비스 테스트 코드 3종 추가했습니다 --- apps/chat/services/message_service.py | 51 +++++++++++ apps/chat/tests/test_chatroom_service.py | 92 +++++++++++++++++++ apps/chat/tests/test_last_read_service.py | 105 ++++++++++++++++++++++ apps/chat/tests/test_message_service.py | 89 ++++++++++++++++++ 4 files changed, 337 insertions(+) create mode 100644 apps/chat/tests/test_chatroom_service.py create mode 100644 apps/chat/tests/test_last_read_service.py create mode 100644 apps/chat/tests/test_message_service.py diff --git a/apps/chat/services/message_service.py b/apps/chat/services/message_service.py index e69de29b..951e6976 100644 --- a/apps/chat/services/message_service.py +++ b/apps/chat/services/message_service.py @@ -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 diff --git a/apps/chat/tests/test_chatroom_service.py b/apps/chat/tests/test_chatroom_service.py new file mode 100644 index 00000000..ab29c502 --- /dev/null +++ b/apps/chat/tests/test_chatroom_service.py @@ -0,0 +1,92 @@ +from django.core.exceptions import PermissionDenied +from django.test import TestCase + +from apps.chat.models.chat_message import ChatMessage +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="첫 번째 메시지", + ) + + 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) + # 최신 메시지가 message2 + # last_read가 정상적으로 업데이트 됐는지 확인 + from apps.chat.models.last_read_message import LastReadMessage + + record = LastReadMessage.objects.get( + study_group=self.group, + user=self.user, + ) + self.assertEqual(record.message.id, self.msg2.id) diff --git a/apps/chat/tests/test_last_read_service.py b/apps/chat/tests/test_last_read_service.py new file mode 100644 index 00000000..d778d646 --- /dev/null +++ b/apps/chat/tests/test_last_read_service.py @@ -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, + ) diff --git a/apps/chat/tests/test_message_service.py b/apps/chat/tests/test_message_service.py new file mode 100644 index 00000000..5d02c0ea --- /dev/null +++ b/apps/chat/tests/test_message_service.py @@ -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) From 0d160a128848fb751944499cef517b184c5cd7cf Mon Sep 17 00:00:00 2001 From: s4ngmin-9 Date: Wed, 3 Dec 2025 14:02:26 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9C=85=20test:=20ChatRoomService=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81=20=EB=B0=8F=20=EC=BB=A4=EB=B2=84=EB=A6=AC=EC=A7=80=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ChatRoomService 테스트 코트를 전체적으로 정리 후 커버리지 요구 사항에 맞춰 누락 케이스 추가 python manage.py test apps.chat 전체 통과 --- apps/chat/tests/test_chatroom_service.py | 54 ++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/apps/chat/tests/test_chatroom_service.py b/apps/chat/tests/test_chatroom_service.py index ab29c502..1721484f 100644 --- a/apps/chat/tests/test_chatroom_service.py +++ b/apps/chat/tests/test_chatroom_service.py @@ -1,7 +1,10 @@ +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 @@ -51,6 +54,7 @@ def setUp(self) -> None: sender=self.user, content="첫 번째 메시지", ) + time.sleep(0.001) # 생성 시간 차이를 내려고 넣었습니다 self.msg2 = ChatMessage.objects.create( study_group=self.group, @@ -81,12 +85,56 @@ def test_get_room_info(self) -> None: def test_mark_all_read(self) -> None: ChatRoomService.mark_all_read(self.group, self.user) - # 최신 메시지가 message2 - # last_read가 정상적으로 업데이트 됐는지 확인 - from apps.chat.models.last_read_message import LastReadMessage 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: + # 채팅방 목록 조회 기본 테스트 + result = ChatRoomService.get_chatrooms(self.user) + + self.assertEqual(len(result), 1) + self.assertEqual(result[0]["group_name"], self.group.name) + self.assertEqual(result[0]["last_message_content"], self.msg2.content) + + 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) From 9b69e57374cb36c3c4b6577d6c67916cc10e737c Mon Sep 17 00:00:00 2001 From: s4ngmin-9 Date: Wed, 3 Dec 2025 15:51:41 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20ChatRoomSe?= =?UTF-8?q?rvice.get=5Fchatrooms=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B3=B4?= =?UTF-8?q?=EC=B6=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 리뷰 내용 반영해서 get_chatrooms 테이트 보강했습니다 --- apps/chat/tests/test_chatroom_service.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/chat/tests/test_chatroom_service.py b/apps/chat/tests/test_chatroom_service.py index 1721484f..57822200 100644 --- a/apps/chat/tests/test_chatroom_service.py +++ b/apps/chat/tests/test_chatroom_service.py @@ -124,8 +124,19 @@ def test_get_chatrooms_basic(self) -> None: result = ChatRoomService.get_chatrooms(self.user) self.assertEqual(len(result), 1) - self.assertEqual(result[0]["group_name"], self.group.name) - self.assertEqual(result[0]["last_message_content"], self.msg2.content) + + 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(