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