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

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