feat(user-creator-chat): WebSocket Redis presence를 추가한다
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
package kr.co.vividnext.sodalive.v2.usercreatorchat.websocket
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Mockito
|
||||
import org.springframework.data.redis.core.SetOperations
|
||||
import org.springframework.data.redis.core.StringRedisTemplate
|
||||
import org.springframework.data.redis.core.ValueOperations
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
class UserCreatorChatPresenceServiceTest {
|
||||
private val stringRedisTemplate = Mockito.mock(StringRedisTemplate::class.java)
|
||||
private val valueOperations = Mockito.mock(ValueOperations::class.java) as ValueOperations<String, String>
|
||||
private val setOperations = Mockito.mock(SetOperations::class.java) as SetOperations<String, String>
|
||||
private val objectMapper = ObjectMapper()
|
||||
.findAndRegisterModules()
|
||||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||
private val service = UserCreatorChatPresenceService(stringRedisTemplate, objectMapper, "test-server")
|
||||
|
||||
@Test
|
||||
@DisplayName("join 시 session presence와 member session index에 TTL을 설정한다")
|
||||
fun shouldMarkJoinedWithTtl() {
|
||||
givenRedisOperations()
|
||||
|
||||
service.markJoined(roomId = 10L, memberId = 20L, sessionId = "session-1")
|
||||
|
||||
val presenceJsonCaptor = ArgumentCaptor.forClass(String::class.java)
|
||||
Mockito.verify(valueOperations).set(
|
||||
Mockito.eq("v2:user-creator-chat:ws:presence:10:20:session-1"),
|
||||
presenceJsonCaptor.capture(),
|
||||
Mockito.eq(Duration.ofSeconds(90))
|
||||
)
|
||||
assertPresenceJson(presenceJsonCaptor.value)
|
||||
Mockito.verify(setOperations).add("v2:user-creator-chat:ws:room:10:member:20:sessions", "session-1")
|
||||
Mockito.verify(stringRedisTemplate).expire(
|
||||
"v2:user-creator-chat:ws:room:10:member:20:sessions",
|
||||
Duration.ofSeconds(90)
|
||||
)
|
||||
Mockito.verify(setOperations).add("v2:user-creator-chat:ws:room:10", "20")
|
||||
Mockito.verify(stringRedisTemplate).expire("v2:user-creator-chat:ws:room:10", Duration.ofSeconds(90))
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("refresh 시 기존 session presence와 index TTL을 갱신한다")
|
||||
fun shouldRefreshPresenceTtl() {
|
||||
givenRedisOperations()
|
||||
|
||||
service.refresh(roomId = 10L, memberId = 20L, sessionId = "session-1")
|
||||
|
||||
val presenceJsonCaptor = ArgumentCaptor.forClass(String::class.java)
|
||||
Mockito.verify(valueOperations).set(
|
||||
Mockito.eq("v2:user-creator-chat:ws:presence:10:20:session-1"),
|
||||
presenceJsonCaptor.capture(),
|
||||
Mockito.eq(Duration.ofSeconds(90))
|
||||
)
|
||||
assertPresenceJson(presenceJsonCaptor.value)
|
||||
Mockito.verify(stringRedisTemplate).expire(
|
||||
"v2:user-creator-chat:ws:room:10:member:20:sessions",
|
||||
Duration.ofSeconds(90)
|
||||
)
|
||||
Mockito.verify(stringRedisTemplate).expire("v2:user-creator-chat:ws:room:10", Duration.ofSeconds(90))
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("leave 시 session presence를 삭제하고 마지막 session이면 member presence를 제거한다")
|
||||
fun shouldMarkLeftAndRemoveMemberPresenceWhenLastSessionLeaves() {
|
||||
givenRedisOperations()
|
||||
Mockito.`when`(setOperations.size("v2:user-creator-chat:ws:room:10:member:20:sessions")).thenReturn(0L)
|
||||
|
||||
service.markLeft(roomId = 10L, memberId = 20L, sessionId = "session-1")
|
||||
|
||||
Mockito.verify(stringRedisTemplate).delete("v2:user-creator-chat:ws:presence:10:20:session-1")
|
||||
Mockito.verify(setOperations).remove("v2:user-creator-chat:ws:room:10:member:20:sessions", "session-1")
|
||||
Mockito.verify(stringRedisTemplate).delete("v2:user-creator-chat:ws:room:10:member:20:sessions")
|
||||
Mockito.verify(setOperations).remove("v2:user-creator-chat:ws:room:10", "20")
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("leave 후 남은 session id가 stale이면 member presence를 제거한다")
|
||||
fun shouldRemoveMemberPresenceWhenRemainingSessionIdsAreStale() {
|
||||
givenRedisOperations()
|
||||
Mockito.`when`(setOperations.size("v2:user-creator-chat:ws:room:10:member:20:sessions")).thenReturn(1L)
|
||||
Mockito.`when`(setOperations.members("v2:user-creator-chat:ws:room:10:member:20:sessions"))
|
||||
.thenReturn(setOf("session-2"))
|
||||
Mockito.`when`(stringRedisTemplate.hasKey("v2:user-creator-chat:ws:presence:10:20:session-2"))
|
||||
.thenReturn(false)
|
||||
|
||||
service.markLeft(roomId = 10L, memberId = 20L, sessionId = "session-1")
|
||||
|
||||
Mockito.verify(setOperations).remove("v2:user-creator-chat:ws:room:10:member:20:sessions", "session-2")
|
||||
Mockito.verify(stringRedisTemplate).delete("v2:user-creator-chat:ws:room:10:member:20:sessions")
|
||||
Mockito.verify(setOperations).remove("v2:user-creator-chat:ws:room:10", "20")
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("member session index에 live session key가 있으면 room presence가 있다고 판단한다")
|
||||
fun shouldReturnPresenceFromMemberSessionIndex() {
|
||||
givenRedisOperations()
|
||||
Mockito.`when`(setOperations.members("v2:user-creator-chat:ws:room:10:member:20:sessions"))
|
||||
.thenReturn(setOf("session-1"))
|
||||
Mockito.`when`(stringRedisTemplate.hasKey("v2:user-creator-chat:ws:presence:10:20:session-1"))
|
||||
.thenReturn(true)
|
||||
|
||||
val hasPresence = service.hasPresence(roomId = 10L, memberId = 20L)
|
||||
|
||||
org.junit.jupiter.api.Assertions.assertEquals(true, hasPresence, "Expected member presence to be true")
|
||||
}
|
||||
|
||||
private fun givenRedisOperations() {
|
||||
Mockito.`when`(stringRedisTemplate.opsForValue()).thenReturn(valueOperations)
|
||||
Mockito.`when`(stringRedisTemplate.opsForSet()).thenReturn(setOperations)
|
||||
}
|
||||
|
||||
private fun assertPresenceJson(json: String) {
|
||||
val presence = objectMapper.readTree(json)
|
||||
assertEquals("test-server", presence["serverId"].asText())
|
||||
assertEquals(20L, presence["memberId"].asLong())
|
||||
assertEquals(10L, presence["roomId"].asLong())
|
||||
assertEquals("session-1", presence["sessionId"].asText())
|
||||
assertNotNull(Instant.parse(presence["lastSeenAt"].asText()))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user