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.

CapabilitiesHandlerTest.kt 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. package com.dmdirc.ktirc.events
  2. import com.dmdirc.ktirc.IrcClient
  3. import com.dmdirc.ktirc.TestConstants
  4. import com.dmdirc.ktirc.model.CapabilitiesNegotiationState
  5. import com.dmdirc.ktirc.model.Capability
  6. import com.dmdirc.ktirc.model.ServerState
  7. import com.dmdirc.ktirc.sasl.SaslMechanism
  8. import com.dmdirc.ktirc.sasl.fromBase64
  9. import com.dmdirc.ktirc.sasl.toBase64
  10. import com.nhaarman.mockitokotlin2.*
  11. import org.junit.jupiter.api.Assertions.*
  12. import org.junit.jupiter.api.Test
  13. internal class CapabilitiesHandlerTest {
  14. private val saslMech1 = mock<SaslMechanism> {
  15. on { priority } doReturn 1
  16. on { ircName } doReturn "mech1"
  17. }
  18. private val saslMech2 = mock<SaslMechanism> {
  19. on { priority } doReturn 2
  20. on { ircName } doReturn "mech2"
  21. }
  22. private val saslMech3 = mock<SaslMechanism> {
  23. on { priority } doReturn 3
  24. on { ircName } doReturn "mech3"
  25. }
  26. private val handler = CapabilitiesHandler()
  27. private val serverState = ServerState("", "", null)
  28. private val ircClient = mock<IrcClient> {
  29. on { serverState } doReturn serverState
  30. }
  31. @Test
  32. fun `adds new capabilities to the state`() {
  33. handler.processEvent(ircClient, ServerCapabilitiesReceived(TestConstants.time, hashMapOf(
  34. Capability.EchoMessages to "",
  35. Capability.HostsInNamesReply to "123"
  36. )))
  37. assertEquals(2, serverState.capabilities.advertisedCapabilities.size)
  38. assertEquals("", serverState.capabilities.advertisedCapabilities[Capability.EchoMessages])
  39. assertEquals("123", serverState.capabilities.advertisedCapabilities[Capability.HostsInNamesReply])
  40. }
  41. @Test
  42. fun `updates negotiation state when capabilities finished`() {
  43. serverState.capabilities.advertisedCapabilities[Capability.EchoMessages] = ""
  44. handler.processEvent(ircClient, ServerCapabilitiesFinished(TestConstants.time))
  45. assertEquals(CapabilitiesNegotiationState.AWAITING_ACK, serverState.capabilities.negotiationState)
  46. }
  47. @Test
  48. fun `sends REQ when capabilities received`() {
  49. serverState.capabilities.advertisedCapabilities[Capability.EchoMessages] = ""
  50. serverState.capabilities.advertisedCapabilities[Capability.AccountChangeMessages] = ""
  51. handler.processEvent(ircClient, ServerCapabilitiesFinished(TestConstants.time))
  52. verify(ircClient).send(argThat { equals("CAP REQ :echo-message account-notify") || equals("CAP REQ :account-notify echo-message") })
  53. }
  54. @Test
  55. fun `sends END when blank capabilities received`() {
  56. handler.processEvent(ircClient, ServerCapabilitiesFinished(TestConstants.time))
  57. verify(ircClient).send("CAP END")
  58. }
  59. @Test
  60. fun `updates negotiation when blank capabilities received`() {
  61. handler.processEvent(ircClient, ServerCapabilitiesFinished(TestConstants.time))
  62. assertEquals(CapabilitiesNegotiationState.FINISHED, serverState.capabilities.negotiationState)
  63. }
  64. @Test
  65. fun `sends END when capabilities acknowledged and no profile`() {
  66. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  67. Capability.EchoMessages to "",
  68. Capability.HostsInNamesReply to "123"
  69. )))
  70. verify(ircClient).send("CAP END")
  71. }
  72. @Test
  73. fun `sends END when capabilities acknowledged and no sasl state`() {
  74. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  75. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  76. Capability.EchoMessages to "",
  77. Capability.HostsInNamesReply to "123"
  78. )))
  79. verify(ircClient).send("CAP END")
  80. }
  81. @Test
  82. fun `sends END when capabilities acknowledged and no shared mechanism`() {
  83. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  84. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  85. Capability.SaslAuthentication to "fake1,fake2",
  86. Capability.HostsInNamesReply to "123"
  87. )))
  88. verify(ircClient).send("CAP END")
  89. }
  90. @Test
  91. fun `sends AUTHENTICATE when capabilities acknowledged with shared mechanism`() {
  92. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  93. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  94. Capability.SaslAuthentication to "mech1,fake2",
  95. Capability.HostsInNamesReply to "123"
  96. )))
  97. verify(ircClient).send("AUTHENTICATE mech1")
  98. }
  99. @Test
  100. fun `sets current SASL mechanism when capabilities acknowledged with shared mechanism`() {
  101. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  102. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  103. Capability.SaslAuthentication to "mech1,fake2",
  104. Capability.HostsInNamesReply to "123"
  105. )))
  106. assertSame(saslMech1, serverState.sasl.currentMechanism)
  107. }
  108. @Test
  109. fun `sends authenticate when capabilities acknowledged with shared mechanism`() {
  110. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  111. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  112. Capability.SaslAuthentication to "mech1,fake2",
  113. Capability.HostsInNamesReply to "123"
  114. )))
  115. verify(ircClient).send("AUTHENTICATE mech1")
  116. }
  117. @Test
  118. fun `updates negotiation state when capabilities acknowledged with shared mechanism`() {
  119. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  120. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  121. Capability.SaslAuthentication to "mech1,fake2",
  122. Capability.HostsInNamesReply to "123"
  123. )))
  124. assertEquals(CapabilitiesNegotiationState.AUTHENTICATING, serverState.capabilities.negotiationState)
  125. }
  126. @Test
  127. fun `updates negotiation state when capabilities acknowledged`() {
  128. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  129. Capability.EchoMessages to "",
  130. Capability.HostsInNamesReply to "123"
  131. )))
  132. assertEquals(CapabilitiesNegotiationState.FINISHED, serverState.capabilities.negotiationState)
  133. }
  134. @Test
  135. fun `stores enabled caps when capabilities acknowledged`() {
  136. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  137. Capability.EchoMessages to "",
  138. Capability.HostsInNamesReply to "123"
  139. )))
  140. assertEquals(2, serverState.capabilities.enabledCapabilities.size)
  141. assertEquals("", serverState.capabilities.enabledCapabilities[Capability.EchoMessages])
  142. assertEquals("123", serverState.capabilities.enabledCapabilities[Capability.HostsInNamesReply])
  143. }
  144. @Test
  145. fun `aborts authentication attempt if not expecting one`() {
  146. serverState.sasl.currentMechanism = null
  147. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, "+"))
  148. verify(ircClient).send("AUTHENTICATE *")
  149. }
  150. @Test
  151. fun `passes authentication message to mechanism if in auth process`() {
  152. serverState.sasl.currentMechanism = saslMech1
  153. val argument = "ABC"
  154. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, argument))
  155. verify(saslMech1).handleAuthenticationEvent(ircClient, argument.fromBase64())
  156. }
  157. @Test
  158. fun `stores partial authentication message if it's 400 bytes long`() {
  159. serverState.sasl.currentMechanism = saslMech1
  160. val argument = "A".repeat(400)
  161. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, argument))
  162. assertEquals(argument, serverState.sasl.saslBuffer)
  163. verify(saslMech1, never()).handleAuthenticationEvent(any(), any())
  164. }
  165. @Test
  166. fun `appends authentication messages if it's 400 bytes long and data already exists`() {
  167. serverState.sasl.currentMechanism = saslMech1
  168. serverState.sasl.saslBuffer = "A".repeat(400)
  169. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, "B".repeat(400)))
  170. assertEquals("A".repeat(400) + "B".repeat(400), serverState.sasl.saslBuffer)
  171. verify(saslMech1, never()).handleAuthenticationEvent(any(), any())
  172. }
  173. @Test
  174. fun `reconstructs partial authentication message to mechanism if data stored and partial received`() {
  175. serverState.sasl.currentMechanism = saslMech1
  176. serverState.sasl.saslBuffer = "A".repeat(400)
  177. val argument = "ABCD"
  178. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, argument))
  179. val captor = argumentCaptor<ByteArray>()
  180. verify(saslMech1).handleAuthenticationEvent(same(ircClient), captor.capture())
  181. assertEquals("A".repeat(400) + "ABCD", captor.firstValue.toBase64())
  182. }
  183. @Test
  184. fun `reconstructs partial authentication message to mechanism if data stored and null received`() {
  185. serverState.sasl.currentMechanism = saslMech1
  186. serverState.sasl.saslBuffer = "A".repeat(400)
  187. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, null))
  188. val captor = argumentCaptor<ByteArray>()
  189. verify(saslMech1).handleAuthenticationEvent(same(ircClient), captor.capture())
  190. assertEquals("A".repeat(400), captor.firstValue.toBase64())
  191. }
  192. @Test
  193. fun `sends END when SASL auth finished`() {
  194. handler.processEvent(ircClient, SaslFinished(TestConstants.time, true))
  195. verify(ircClient).send("CAP END")
  196. }
  197. @Test
  198. fun `sets negotiation state when SASL auth finished`() {
  199. handler.processEvent(ircClient, SaslFinished(TestConstants.time, true))
  200. assertEquals(CapabilitiesNegotiationState.FINISHED, serverState.capabilities.negotiationState)
  201. }
  202. @Test
  203. fun `resets SASL state when SASL auth finished`() {
  204. with (serverState.sasl) {
  205. currentMechanism = saslMech1
  206. saslBuffer = "HackThePlanet"
  207. mechanismState = "root@thegibson"
  208. }
  209. handler.processEvent(ircClient, SaslFinished(TestConstants.time, true))
  210. with (serverState.sasl) {
  211. assertNull(currentMechanism)
  212. assertEquals("", saslBuffer)
  213. assertNull(mechanismState)
  214. }
  215. }
  216. @Test
  217. fun `sends a new authenticate request when sasl mechanism rejected and new one is acceptable`() {
  218. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  219. handler.processEvent(ircClient, SaslMechanismNotAvailableError(TestConstants.time, listOf("mech1", "fake2")))
  220. verify(ircClient).send("AUTHENTICATE mech1")
  221. }
  222. @Test
  223. fun `sends cap end when sasl mechanism rejected and no new one is acceptable`() {
  224. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  225. handler.processEvent(ircClient, SaslMechanismNotAvailableError(TestConstants.time, listOf("fake1", "fake2")))
  226. verify(ircClient).send("CAP END")
  227. }
  228. @Test
  229. fun `sets negotiation state when sasl mechanism rejected and no new one is acceptable`() {
  230. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  231. handler.processEvent(ircClient, SaslMechanismNotAvailableError(TestConstants.time, listOf("fake1", "fake2")))
  232. assertEquals(CapabilitiesNegotiationState.FINISHED, serverState.capabilities.negotiationState)
  233. }
  234. }