You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

IrcClientTest.kt 8.9KB

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