-
Notifications
You must be signed in to change notification settings - Fork 0
[feat/chat_services] 메시지 생성/조회 서비스 및 테스트코드 3종 추가 #103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+396
−0
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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: | ||
| # 채팅방 목록 조회 기본 테스트 | ||
| 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) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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가 전체 메시지 개수인지도
있으면 좋을거 같아요