David Liu

tests for signal client message flow

@@ -4,15 +4,19 @@ import com.google.protobuf.util.JsonFormat @@ -4,15 +4,19 @@ import com.google.protobuf.util.JsonFormat
4 import io.livekit.android.util.toOkioByteString 4 import io.livekit.android.util.toOkioByteString
5 import kotlinx.coroutines.ExperimentalCoroutinesApi 5 import kotlinx.coroutines.ExperimentalCoroutinesApi
6 import kotlinx.coroutines.async 6 import kotlinx.coroutines.async
  7 +import kotlinx.coroutines.test.TestCoroutineDispatcher
7 import kotlinx.coroutines.test.TestCoroutineScope 8 import kotlinx.coroutines.test.TestCoroutineScope
8 import kotlinx.coroutines.test.runBlockingTest 9 import kotlinx.coroutines.test.runBlockingTest
9 import kotlinx.serialization.json.Json 10 import kotlinx.serialization.json.Json
10 import livekit.LivekitRtc 11 import livekit.LivekitRtc
11 import okhttp3.* 12 import okhttp3.*
  13 +import org.junit.After
12 import org.junit.Assert 14 import org.junit.Assert
13 import org.junit.Before 15 import org.junit.Before
14 import org.junit.Test 16 import org.junit.Test
15 import org.mockito.Mockito 17 import org.mockito.Mockito
  18 +import org.mockito.kotlin.argThat
  19 +import org.webrtc.SessionDescription
16 20
17 @ExperimentalCoroutinesApi 21 @ExperimentalCoroutinesApi
18 class SignalClientTest { 22 class SignalClientTest {
@@ -22,6 +26,9 @@ class SignalClientTest { @@ -22,6 +26,9 @@ class SignalClientTest {
22 lateinit var listener: SignalClient.Listener 26 lateinit var listener: SignalClient.Listener
23 lateinit var okHttpClient: OkHttpClient 27 lateinit var okHttpClient: OkHttpClient
24 28
  29 + lateinit var coroutineDispatcher: TestCoroutineDispatcher
  30 + lateinit var coroutineScope: TestCoroutineScope
  31 +
25 class MockWebsocketFactory : WebSocket.Factory { 32 class MockWebsocketFactory : WebSocket.Factory {
26 lateinit var ws: WebSocket 33 lateinit var ws: WebSocket
27 lateinit var request: Request 34 lateinit var request: Request
@@ -34,6 +41,8 @@ class SignalClientTest { @@ -34,6 +41,8 @@ class SignalClientTest {
34 41
35 @Before 42 @Before
36 fun setup() { 43 fun setup() {
  44 + coroutineDispatcher = TestCoroutineDispatcher()
  45 + coroutineScope = TestCoroutineScope(coroutineDispatcher)
37 wsFactory = MockWebsocketFactory() 46 wsFactory = MockWebsocketFactory()
38 okHttpClient = Mockito.mock(OkHttpClient::class.java) 47 okHttpClient = Mockito.mock(OkHttpClient::class.java)
39 client = SignalClient( 48 client = SignalClient(
@@ -43,11 +52,17 @@ class SignalClientTest { @@ -43,11 +52,17 @@ class SignalClientTest {
43 Json, 52 Json,
44 useJson = false, 53 useJson = false,
45 okHttpClient = okHttpClient, 54 okHttpClient = okHttpClient,
  55 + ioDispatcher = coroutineDispatcher
46 ) 56 )
47 listener = Mockito.mock(SignalClient.Listener::class.java) 57 listener = Mockito.mock(SignalClient.Listener::class.java)
48 client.listener = listener 58 client.listener = listener
49 } 59 }
50 60
  61 + @After
  62 + fun tearDown() {
  63 + coroutineScope.cleanupTestCoroutines()
  64 + }
  65 +
51 private fun createOpenResponse(request: Request): Response { 66 private fun createOpenResponse(request: Request): Response {
52 return Response.Builder() 67 return Response.Builder()
53 .request(request) 68 .request(request)
@@ -59,13 +74,11 @@ class SignalClientTest { @@ -59,13 +74,11 @@ class SignalClientTest {
59 74
60 @Test 75 @Test
61 fun joinAndResponse() { 76 fun joinAndResponse() {
62 - val job = TestCoroutineScope().async {  
63 - client.join("http://www.example.com", "", null) 77 + val job = coroutineScope.async {
  78 + client.join(EXAMPLE_URL, "", null)
64 } 79 }
65 - client.onOpen(  
66 - wsFactory.ws,  
67 - createOpenResponse(wsFactory.request)  
68 - ) 80 +
  81 + client.onOpen(wsFactory.ws, createOpenResponse(wsFactory.request))
69 client.onMessage(wsFactory.ws, JOIN.toOkioByteString()) 82 client.onMessage(wsFactory.ws, JOIN.toOkioByteString())
70 83
71 runBlockingTest { 84 runBlockingTest {
@@ -76,21 +89,55 @@ class SignalClientTest { @@ -76,21 +89,55 @@ class SignalClientTest {
76 89
77 @Test 90 @Test
78 fun reconnect() { 91 fun reconnect() {
79 - val job = TestCoroutineScope().async {  
80 - client.reconnect("http://www.example.com", "") 92 + val job = coroutineScope.async {
  93 + client.reconnect(EXAMPLE_URL, "")
81 } 94 }
82 - client.onOpen(  
83 - wsFactory.ws,  
84 - createOpenResponse(wsFactory.request)  
85 - ) 95 +
  96 + client.onOpen(wsFactory.ws, createOpenResponse(wsFactory.request))
  97 +
86 runBlockingTest { 98 runBlockingTest {
87 job.await() 99 job.await()
88 } 100 }
89 } 101 }
90 102
  103 + @Test
  104 + fun listenerNotCalledUntilOnReady() {
  105 + val listener = Mockito.mock(SignalClient.Listener::class.java)
  106 + client.listener = listener
  107 +
  108 + val job = coroutineScope.async {
  109 + client.join(EXAMPLE_URL, "", null)
  110 + }
  111 + client.onOpen(wsFactory.ws, createOpenResponse(wsFactory.request))
  112 + client.onMessage(wsFactory.ws, JOIN.toOkioByteString())
  113 + client.onMessage(wsFactory.ws, OFFER.toOkioByteString())
  114 +
  115 + runBlockingTest { job.await() }
  116 +
  117 + Mockito.verifyNoInteractions(listener)
  118 + }
  119 +
  120 + @Test
  121 + fun listenerCalledAfterOnReady() {
  122 + val listener = Mockito.mock(SignalClient.Listener::class.java)
  123 + client.listener = listener
  124 +
  125 + val job = coroutineScope.async {
  126 + client.join(EXAMPLE_URL, "", null)
  127 + }
  128 + client.onOpen(wsFactory.ws, createOpenResponse(wsFactory.request))
  129 + client.onMessage(wsFactory.ws, JOIN.toOkioByteString())
  130 + client.onMessage(wsFactory.ws, OFFER.toOkioByteString())
  131 +
  132 + runBlockingTest { job.await() }
  133 + client.onReady()
  134 + Mockito.verify(listener)
  135 + .onOffer(argThat { type == SessionDescription.Type.OFFER && description == OFFER.offer.sdp })
  136 + }
  137 +
91 // mock data 138 // mock data
92 companion object { 139 companion object {
93 - private val EXAMPLE_URL = "http://www.example.com" 140 + private const val EXAMPLE_URL = "http://www.example.com"
94 141
95 private val JOIN = with(LivekitRtc.SignalResponse.newBuilder()) { 142 private val JOIN = with(LivekitRtc.SignalResponse.newBuilder()) {
96 join = with(joinBuilder) { 143 join = with(joinBuilder) {
@@ -103,5 +150,13 @@ class SignalClientTest { @@ -103,5 +150,13 @@ class SignalClientTest {
103 } 150 }
104 build() 151 build()
105 } 152 }
  153 + private val OFFER = with(LivekitRtc.SignalResponse.newBuilder()) {
  154 + offer = with(offerBuilder) {
  155 + sdp = ""
  156 + type = "offer"
  157 + build()
  158 + }
  159 + build()
  160 + }
106 } 161 }
107 } 162 }