From a289849a07349d683e1319833e0ab838ccc36e34 Mon Sep 17 00:00:00 2001 From: klaus Date: Wed, 10 Jun 2026 18:11:47 +0900 Subject: [PATCH] =?UTF-8?q?feat(chat):=20DM=20=EC=B1=84=ED=8C=85=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EC=86=8C=EB=A5=BC=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v2/main/chat/dm/data/DmChatRepository.kt | 60 ++++++ .../v2/main/chat/dm/DmChatRepositoryTest.kt | 171 ++++++++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/data/DmChatRepository.kt create mode 100644 app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRepositoryTest.kt diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/data/DmChatRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/data/DmChatRepository.kt new file mode 100644 index 00000000..4fe09ded --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/data/DmChatRepository.kt @@ -0,0 +1,60 @@ +package kr.co.vividnext.sodalive.v2.main.chat.dm.data + +import io.reactivex.rxjava3.core.Single +import kr.co.vividnext.sodalive.common.ApiResponse + +class DmChatRepository(private val api: DmChatApi) { + fun createOrGetRoom( + token: String, + creatorId: Long + ): Single> = api.createDmChatRoom( + authHeader = bearer(token), + request = CreateDmChatRoomRequest(creatorId = creatorId) + ) + + fun openRoom( + token: String, + roomId: Long, + limit: Int = DEFAULT_LIMIT + ): Single> = api.openDmChatRoom( + authHeader = bearer(token), + roomId = roomId, + limit = limit + ) + + fun getMessages( + token: String, + roomId: Long, + cursor: Long?, + limit: Int = DEFAULT_LIMIT + ): Single> = api.getDmChatMessages( + authHeader = bearer(token), + roomId = roomId, + cursor = cursor, + limit = limit + ) + + fun sendTextMessage( + token: String, + roomId: Long, + textMessage: String + ): Single> = api.sendDmTextMessage( + authHeader = bearer(token), + roomId = roomId, + request = SendDmTextMessageRequest(textMessage = textMessage) + ) + + fun disconnectRealtime( + token: String, + roomId: Long + ): Single> = api.disconnectRealtime( + authHeader = bearer(token), + roomId = roomId + ) + + private fun bearer(token: String) = "Bearer $token" + + private companion object { + const val DEFAULT_LIMIT = 20 + } +} diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRepositoryTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRepositoryTest.kt new file mode 100644 index 00000000..a423274b --- /dev/null +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRepositoryTest.kt @@ -0,0 +1,171 @@ +package kr.co.vividnext.sodalive.v2.main.chat.dm + +import io.reactivex.rxjava3.core.Single +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.v2.main.chat.dm.data.CreateDmChatRoomRequest +import kr.co.vividnext.sodalive.v2.main.chat.dm.data.CreateDmChatRoomResponse +import kr.co.vividnext.sodalive.v2.main.chat.dm.data.DmChatApi +import kr.co.vividnext.sodalive.v2.main.chat.dm.data.DmChatMessageResponse +import kr.co.vividnext.sodalive.v2.main.chat.dm.data.DmChatMessagesPageResponse +import kr.co.vividnext.sodalive.v2.main.chat.dm.data.DmChatRepository +import kr.co.vividnext.sodalive.v2.main.chat.dm.data.DmChatRoomOpenResponse +import kr.co.vividnext.sodalive.v2.main.chat.dm.data.SendDmChatMessageResponse +import kr.co.vividnext.sodalive.v2.main.chat.dm.data.SendDmTextMessageRequest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test + +class DmChatRepositoryTest { + + private val api = FakeDmChatApi() + private val repository = DmChatRepository(api) + + @Test + fun `createOrGetRoom은 bearer header와 creatorId를 API에 위임한다`() { + repository.createOrGetRoom(token = "test-token", creatorId = 11L).blockingGet() + + assertEquals("Bearer test-token", api.lastAuthHeader) + assertEquals(CreateDmChatRoomRequest(creatorId = 11L), api.lastCreateRequest) + } + + @Test + fun `openRoom은 기본 limit 20과 bearer header를 API에 위임한다`() { + repository.openRoom(token = "test-token", roomId = 12L).blockingGet() + + assertEquals("Bearer test-token", api.lastAuthHeader) + assertEquals(12L, api.lastRoomId) + assertEquals(20, api.lastLimit) + } + + @Test + fun `getMessages는 cursor와 limit을 API에 위임한다`() { + repository.getMessages(token = "test-token", roomId = 12L, cursor = 30L, limit = 10).blockingGet() + + assertEquals("Bearer test-token", api.lastAuthHeader) + assertEquals(12L, api.lastRoomId) + assertEquals(30L, api.lastCursor) + assertEquals(10, api.lastLimit) + } + + @Test + fun `sendTextMessage는 textMessage request를 API에 위임한다`() { + repository.sendTextMessage(token = "test-token", roomId = 12L, textMessage = "안녕").blockingGet() + + assertEquals("Bearer test-token", api.lastAuthHeader) + assertEquals(12L, api.lastRoomId) + assertEquals(SendDmTextMessageRequest(textMessage = "안녕"), api.lastSendRequest) + } + + @Test + fun `disconnectRealtime은 bearer header와 roomId를 API에 위임한다`() { + repository.disconnectRealtime(token = "test-token", roomId = 12L).blockingGet() + + assertEquals("Bearer test-token", api.lastAuthHeader) + assertEquals(12L, api.lastRoomId) + assertNull(api.lastSendRequest) + } + + private class FakeDmChatApi : DmChatApi { + var lastAuthHeader: String? = null + var lastCreateRequest: CreateDmChatRoomRequest? = null + var lastSendRequest: SendDmTextMessageRequest? = null + var lastRoomId: Long? = null + var lastCursor: Long? = null + var lastLimit: Int? = null + + override fun createDmChatRoom( + authHeader: String, + request: CreateDmChatRoomRequest + ): Single> { + lastAuthHeader = authHeader + lastCreateRequest = request + return Single.just(ApiResponse(success = true, data = CreateDmChatRoomResponse(roomId = 12L))) + } + + override fun openDmChatRoom( + authHeader: String, + roomId: Long, + limit: Int + ): Single> { + lastAuthHeader = authHeader + lastRoomId = roomId + lastLimit = limit + return Single.just( + ApiResponse( + success = true, + data = DmChatRoomOpenResponse( + roomId = roomId, + opponentNickname = "상대", + opponentProfileImageUrl = "https://example.com/profile.png", + messages = emptyList(), + hasMore = false, + nextCursor = null + ) + ) + ) + } + + override fun getDmChatMessages( + authHeader: String, + roomId: Long, + cursor: Long?, + limit: Int + ): Single> { + lastAuthHeader = authHeader + lastRoomId = roomId + lastCursor = cursor + lastLimit = limit + return Single.just( + ApiResponse( + success = true, + data = DmChatMessagesPageResponse( + messages = emptyList(), + hasMore = false, + nextCursor = null + ) + ) + ) + } + + override fun sendDmTextMessage( + authHeader: String, + roomId: Long, + request: SendDmTextMessageRequest + ): Single> { + lastAuthHeader = authHeader + lastRoomId = roomId + lastSendRequest = request + return Single.just( + ApiResponse( + success = true, + data = SendDmChatMessageResponse( + message = message(), + deliveredRealtime = true, + pushSent = false + ) + ) + ) + } + + override fun disconnectRealtime( + authHeader: String, + roomId: Long + ): Single> { + lastAuthHeader = authHeader + lastRoomId = roomId + return Single.just(ApiResponse(success = true, data = true)) + } + + private fun message() = DmChatMessageResponse( + messageId = 1L, + messageType = "TEXT", + mine = true, + createdAt = 1000L, + textMessage = "안녕", + voiceMessageUrl = null, + senderId = 2L, + senderNickname = "나", + senderProfileImageUrl = "https://example.com/profile.png" + ) + } +}