Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

IrcClientTest.kt 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. package com.dmdirc.ktirc
  2. import com.dmdirc.ktirc.events.IrcEvent
  3. import com.dmdirc.ktirc.events.ServerConnected
  4. import com.dmdirc.ktirc.events.ServerWelcome
  5. import com.dmdirc.ktirc.io.CaseMapping
  6. import com.dmdirc.ktirc.io.LineBufferedSocket
  7. import com.dmdirc.ktirc.model.Profile
  8. import com.dmdirc.ktirc.model.Server
  9. import com.dmdirc.ktirc.model.ServerFeature
  10. import com.dmdirc.ktirc.model.User
  11. import com.dmdirc.ktirc.util.currentTimeProvider
  12. import com.nhaarman.mockitokotlin2.*
  13. import kotlinx.coroutines.*
  14. import kotlinx.coroutines.channels.Channel
  15. import org.junit.jupiter.api.Assertions.*
  16. import org.junit.jupiter.api.BeforeEach
  17. import org.junit.jupiter.api.Test
  18. import org.junit.jupiter.api.assertThrows
  19. internal class IrcClientImplTest {
  20. companion object {
  21. private const val HOST = "thegibson.com"
  22. private const val PORT = 12345
  23. private const val NICK = "AcidBurn"
  24. private const val REAL_NAME = "Kate Libby"
  25. private const val USER_NAME = "acidb"
  26. private const val PASSWORD = "HackThePlanet"
  27. }
  28. private val readLineChannel = Channel<ByteArray>(10)
  29. private val mockSocket = mock<LineBufferedSocket> {
  30. on { readLines(any()) } doReturn readLineChannel
  31. }
  32. private val mockSocketFactory = mock<(String, Int, Boolean) -> LineBufferedSocket> {
  33. on { invoke(eq(HOST), eq(PORT), any()) } doReturn mockSocket
  34. }
  35. private val mockEventHandler = mock<(IrcEvent) -> Unit>()
  36. @BeforeEach
  37. fun setUp() {
  38. currentTimeProvider = { TestConstants.time }
  39. }
  40. @Test
  41. fun `IrcClientImpl uses socket factory to create a new socket on connect`() {
  42. val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
  43. client.socketFactory = mockSocketFactory
  44. client.connect()
  45. verify(mockSocketFactory, timeout(500)).invoke(HOST, PORT, false)
  46. }
  47. @Test
  48. fun `IrcClientImpl uses socket factory to create a new tls on connect`() {
  49. val client = IrcClientImpl(Server(HOST, PORT, true), Profile(NICK, REAL_NAME, USER_NAME))
  50. client.socketFactory = mockSocketFactory
  51. client.connect()
  52. verify(mockSocketFactory, timeout(500)).invoke(HOST, PORT, true)
  53. }
  54. @Test
  55. fun `IrcClientImpl throws if socket already exists`() {
  56. val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
  57. client.socketFactory = mockSocketFactory
  58. client.connect()
  59. assertThrows<IllegalStateException> {
  60. client.connect()
  61. }
  62. }
  63. @Test
  64. fun `IrcClientImpl emits connected event with local time`() = runBlocking {
  65. currentTimeProvider = { TestConstants.time }
  66. val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
  67. client.socketFactory = mockSocketFactory
  68. client.onEvent(mockEventHandler)
  69. client.connect()
  70. val captor = argumentCaptor<ServerConnected>()
  71. verify(mockEventHandler, timeout(500)).invoke(captor.capture())
  72. assertEquals(TestConstants.time, captor.firstValue.time)
  73. }
  74. @Test
  75. fun `IrcClientImpl sends basic connection strings`() = runBlocking {
  76. val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
  77. client.socketFactory = mockSocketFactory
  78. client.connect()
  79. client.blockUntilConnected()
  80. assertEquals("CAP LS 302", String(client.writeChannel!!.receive()))
  81. assertEquals("NICK :$NICK", String(client.writeChannel!!.receive()))
  82. assertEquals("USER $USER_NAME localhost $HOST :$REAL_NAME", String(client.writeChannel!!.receive()))
  83. }
  84. @Test
  85. fun `IrcClientImpl sends password first, when present`() = runBlocking {
  86. val client = IrcClientImpl(Server(HOST, PORT, password = PASSWORD), Profile(NICK, REAL_NAME, USER_NAME))
  87. client.socketFactory = mockSocketFactory
  88. client.connect()
  89. client.blockUntilConnected()
  90. assertEquals("CAP LS 302", String(client.writeChannel!!.receive()))
  91. assertEquals("PASS :$PASSWORD", String(client.writeChannel!!.receive()))
  92. }
  93. @Test
  94. fun `IrcClientImpl sends events to provided event handler`() {
  95. val client = IrcClientImpl(Server(HOST, PORT, password = PASSWORD), Profile(NICK, REAL_NAME, USER_NAME))
  96. client.socketFactory = mockSocketFactory
  97. client.onEvent(mockEventHandler)
  98. GlobalScope.launch {
  99. readLineChannel.send(":the.gibson 001 acidBurn :Welcome to the IRC!".toByteArray())
  100. }
  101. client.connect()
  102. verify(mockEventHandler, timeout(500)).invoke(isA<ServerWelcome>())
  103. }
  104. @Test
  105. fun `IrcClient gets case mapping from server features`() {
  106. val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
  107. client.serverState.features[ServerFeature.ServerCaseMapping] = CaseMapping.RfcStrict
  108. assertEquals(CaseMapping.RfcStrict, client.caseMapping)
  109. }
  110. @Test
  111. fun `IrcClient indicates if user is local user or not`() {
  112. val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
  113. client.serverState.localNickname = "[acidBurn]"
  114. assertTrue(client.isLocalUser(User("{acidBurn}", "libby", "root.localhost")))
  115. assertFalse(client.isLocalUser(User("acid-Burn", "libby", "root.localhost")))
  116. }
  117. @Test
  118. fun `IrcClient uses current case mapping to check local user`() {
  119. val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
  120. client.serverState.localNickname = "[acidBurn]"
  121. client.serverState.features[ServerFeature.ServerCaseMapping] = CaseMapping.Ascii
  122. assertFalse(client.isLocalUser(User("{acidBurn}", "libby", "root.localhost")))
  123. }
  124. @Test
  125. fun `IrcClientImpl join blocks when socket is open`() {
  126. val client = IrcClientImpl(Server(HOST, PORT, password = PASSWORD), Profile(NICK, REAL_NAME, USER_NAME))
  127. client.socketFactory = mockSocketFactory
  128. GlobalScope.launch {
  129. readLineChannel.send(":the.gibson 001 acidBurn :Welcome to the IRC!".toByteArray())
  130. }
  131. client.connect()
  132. runBlocking {
  133. assertNull(withTimeoutOrNull(100L) {
  134. client.join()
  135. true
  136. })
  137. }
  138. }
  139. @Test
  140. fun `IrcClientImpl join returns when socket is closed`() {
  141. val client = IrcClientImpl(Server(HOST, PORT, password = PASSWORD), Profile(NICK, REAL_NAME, USER_NAME))
  142. client.socketFactory = mockSocketFactory
  143. GlobalScope.launch {
  144. readLineChannel.send(":the.gibson 001 acidBurn :Welcome to the IRC!".toByteArray())
  145. readLineChannel.close()
  146. }
  147. client.connect()
  148. runBlocking {
  149. assertEquals(true, withTimeoutOrNull(500L) {
  150. client.join()
  151. true
  152. })
  153. }
  154. }
  155. @Test
  156. fun `IrcClientImpl sends text to socket`() = runBlocking {
  157. val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
  158. client.socketFactory = mockSocketFactory
  159. client.connect()
  160. client.blockUntilConnected()
  161. client.send("testing 123")
  162. assertEquals(true, withTimeoutOrNull(500) {
  163. var found = false
  164. for (line in client.writeChannel!!) {
  165. if (String(line) == "testing 123") {
  166. found = true
  167. break
  168. }
  169. }
  170. found
  171. })
  172. }
  173. @Test
  174. fun `IrcClientImpl disconnects the socket`() = runBlocking {
  175. val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
  176. client.socketFactory = mockSocketFactory
  177. client.connect()
  178. client.blockUntilConnected()
  179. client.disconnect()
  180. verify(mockSocket, timeout(500)).disconnect()
  181. }
  182. @Test
  183. fun `IrcClientImpl sends messages in order`() = runBlocking {
  184. val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
  185. client.socketFactory = mockSocketFactory
  186. client.connect()
  187. client.blockUntilConnected()
  188. (0..100).forEach { client.send("TEST $it") }
  189. assertEquals(100, withTimeoutOrNull(500) {
  190. var next = 0
  191. for (line in client.writeChannel!!) {
  192. val stringy = String(line)
  193. if (stringy.startsWith("TEST ")) {
  194. assertEquals("TEST $next", stringy)
  195. if (++next == 100) {
  196. break
  197. }
  198. }
  199. }
  200. next
  201. })
  202. }
  203. private suspend fun IrcClientImpl.blockUntilConnected() {
  204. // Yuck. Maybe connect should be asynchronous?
  205. while (writeChannel == null) {
  206. delay(50)
  207. }
  208. }
  209. }