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 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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 `updates negotiation state 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. assertEquals(CapabilitiesNegotiationState.AUTHENTICATING, serverState.capabilities.negotiationState)
  116. }
  117. @Test
  118. fun `updates negotiation state when capabilities acknowledged`() {
  119. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  120. Capability.EchoMessages to "",
  121. Capability.HostsInNamesReply to "123"
  122. )))
  123. assertEquals(CapabilitiesNegotiationState.FINISHED, serverState.capabilities.negotiationState)
  124. }
  125. @Test
  126. fun `stores enabled caps when capabilities acknowledged`() {
  127. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  128. Capability.EchoMessages to "",
  129. Capability.HostsInNamesReply to "123"
  130. )))
  131. assertEquals(2, serverState.capabilities.enabledCapabilities.size)
  132. assertEquals("", serverState.capabilities.enabledCapabilities[Capability.EchoMessages])
  133. assertEquals("123", serverState.capabilities.enabledCapabilities[Capability.HostsInNamesReply])
  134. }
  135. @Test
  136. fun `aborts authentication attempt if not expecting one`() {
  137. serverState.sasl.currentMechanism = null
  138. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, "+"))
  139. verify(ircClient).send("AUTHENTICATE *")
  140. }
  141. @Test
  142. fun `passes authentication message to mechanism if in auth process`() {
  143. serverState.sasl.currentMechanism = saslMech1
  144. val argument = "ABC"
  145. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, argument))
  146. verify(saslMech1).handleAuthenticationEvent(ircClient, argument.fromBase64())
  147. }
  148. @Test
  149. fun `stores partial authentication message if it's 400 bytes long`() {
  150. serverState.sasl.currentMechanism = saslMech1
  151. val argument = "A".repeat(400)
  152. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, argument))
  153. assertEquals(argument, serverState.sasl.saslBuffer)
  154. verify(saslMech1, never()).handleAuthenticationEvent(any(), any())
  155. }
  156. @Test
  157. fun `appends authentication messages if it's 400 bytes long and data already exists`() {
  158. serverState.sasl.currentMechanism = saslMech1
  159. serverState.sasl.saslBuffer = "A".repeat(400)
  160. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, "B".repeat(400)))
  161. assertEquals("A".repeat(400) + "B".repeat(400), serverState.sasl.saslBuffer)
  162. verify(saslMech1, never()).handleAuthenticationEvent(any(), any())
  163. }
  164. @Test
  165. fun `reconstructs partial authentication message to mechanism if data stored and partial received`() {
  166. serverState.sasl.currentMechanism = saslMech1
  167. serverState.sasl.saslBuffer = "A".repeat(400)
  168. val argument = "ABCD"
  169. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, argument))
  170. val captor = argumentCaptor<ByteArray>()
  171. verify(saslMech1).handleAuthenticationEvent(same(ircClient), captor.capture())
  172. assertEquals("A".repeat(400) + "ABCD", captor.firstValue.toBase64())
  173. }
  174. @Test
  175. fun `reconstructs partial authentication message to mechanism if data stored and null received`() {
  176. serverState.sasl.currentMechanism = saslMech1
  177. serverState.sasl.saslBuffer = "A".repeat(400)
  178. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, null))
  179. val captor = argumentCaptor<ByteArray>()
  180. verify(saslMech1).handleAuthenticationEvent(same(ircClient), captor.capture())
  181. assertEquals("A".repeat(400), captor.firstValue.toBase64())
  182. }
  183. @Test
  184. fun `sends END when SASL auth finished`() {
  185. handler.processEvent(ircClient, SaslFinished(TestConstants.time, true))
  186. verify(ircClient).send("CAP END")
  187. }
  188. @Test
  189. fun `sets negotiation state when SASL auth finished`() {
  190. handler.processEvent(ircClient, SaslFinished(TestConstants.time, true))
  191. assertEquals(CapabilitiesNegotiationState.FINISHED, serverState.capabilities.negotiationState)
  192. }
  193. @Test
  194. fun `resets SASL state when SASL auth finished`() {
  195. with (serverState.sasl) {
  196. currentMechanism = saslMech1
  197. saslBuffer = "HackThePlanet"
  198. mechanismState = "root@thegibson"
  199. }
  200. handler.processEvent(ircClient, SaslFinished(TestConstants.time, true))
  201. with (serverState.sasl) {
  202. assertNull(currentMechanism)
  203. assertEquals("", saslBuffer)
  204. assertNull(mechanismState)
  205. }
  206. }
  207. }