feat(dm): WebSocket 계약 모델을 추가한다

This commit is contained in:
2026-06-18 17:41:31 +09:00
parent 0e03a1a14a
commit e76562067f
2 changed files with 283 additions and 0 deletions

View File

@@ -0,0 +1,168 @@
package kr.co.vividnext.sodalive.v2.main.chat.dm
import com.google.gson.Gson
import kr.co.vividnext.sodalive.v2.main.chat.dm.data.DmChatMessageResponse
import kr.co.vividnext.sodalive.v2.main.chat.dm.data.DmChatSocketEvent
import kr.co.vividnext.sodalive.v2.main.chat.dm.data.DmChatSocketParser
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
class DmChatSocketParserTest {
private val parser = DmChatSocketParser(Gson())
@Test
fun `JOINED type은 joined event로 파싱된다`() {
val event = parser.parse(
"""
{
"type": "JOINED",
"payload": { "roomId": 10 }
}
""".trimIndent()
)
assertEquals(DmChatSocketEvent.Joined, event)
}
@Test
fun `MESSAGE type은 DM 메시지 payload로 파싱된다`() {
val event = parser.parse(
"""
{
"type": "MESSAGE",
"payload": { "message": ${messageJson()} }
}
""".trimIndent()
)
val message = requireMessage(event)
assertEquals(10L, message.messageId)
assertEquals("안녕하세요", message.textMessage)
}
@Test
fun `SEND_ACK type은 requestId와 서버 확정 메시지로 파싱된다`() {
val event = parser.parse(
"""
{
"type": "SEND_ACK",
"payload": {
"requestId": "request-1",
"message": ${messageJson(messageId = 11L, textMessage = "확정")}
}
}
""".trimIndent()
)
val ack = event as? DmChatSocketEvent.SendAck
requireNotNull(ack)
assertEquals("request-1", ack.requestId)
assertEquals(11L, ack.message.messageId)
assertEquals("확정", ack.message.textMessage)
}
@Test
fun `ERROR type은 nullable requestId와 code message를 보존한다`() {
val event = parser.parse(
"""
{
"type": "ERROR",
"payload": {
"requestId": null,
"code": "INVALID_MESSAGE",
"message": "메시지를 전송할 수 없습니다"
}
}
""".trimIndent()
)
val error = event as? DmChatSocketEvent.Error
requireNotNull(error)
assertNull(error.requestId)
assertEquals("INVALID_MESSAGE", error.code)
assertEquals("메시지를 전송할 수 없습니다", error.message)
}
@Test
fun `ERROR type은 nullable code와 message를 보존한다`() {
val event = parser.parse(
"""
{
"type": "ERROR",
"payload": {
"requestId": "request-1",
"code": null,
"message": null
}
}
""".trimIndent()
)
val error = event as? DmChatSocketEvent.Error
requireNotNull(error)
assertEquals("request-1", error.requestId)
assertNull(error.code)
assertNull(error.message)
}
@Test
fun `PONG type은 pong event로 파싱된다`() {
val event = parser.parse(
"""
{
"type": "PONG",
"payload": {}
}
""".trimIndent()
)
assertEquals(DmChatSocketEvent.Pong, event)
}
@Test
fun `알 수 없는 type은 null로 무시된다`() {
val event = parser.parse(
"""
{
"type": "UNKNOWN",
"payload": {}
}
""".trimIndent()
)
assertNull(event)
}
@Test
fun `잘못된 JSON은 null로 무시된다`() {
val event = parser.parse("{not-json}")
assertNull(event)
}
private fun requireMessage(event: DmChatSocketEvent?): DmChatMessageResponse {
val messageEvent = event as? DmChatSocketEvent.Message
requireNotNull(messageEvent)
return messageEvent.message
}
private fun messageJson(
messageId: Long = 10L,
textMessage: String = "안녕하세요"
): String =
"""
{
"messageId": $messageId,
"messageType": "TEXT",
"mine": false,
"createdAt": 1000,
"textMessage": "$textMessage",
"voiceMessageUrl": null,
"senderId": 20,
"senderNickname": "크리에이터",
"senderProfileImageUrl": "https://example.com/profile.png"
}
""".trimIndent().replace("\n", "")
}