Browse Source

Reset state on disconnect

tags/v0.6.0
Chris Smith 5 years ago
parent
commit
23bf2faa2c

+ 1
- 0
CHANGELOG View File

@@ -11,6 +11,7 @@ vNEXT (in development)
11 11
     * Added sendTagMessage() to send message tags without any content
12 12
     * The reply() utility automatically marks messages as a reply
13 13
     * Added react() utility to send a reaction client tag
14
+ * State is now reset when the client is disconnected, so you can immediately reconnect
14 15
  * (Internal) improved how coroutines and channels are used in LineBufferedSocket
15 16
 
16 17
 v0.5.0

+ 13
- 1
src/main/kotlin/com/dmdirc/ktirc/IrcClient.kt View File

@@ -5,6 +5,7 @@ import com.dmdirc.ktirc.io.*
5 5
 import com.dmdirc.ktirc.messages.*
6 6
 import com.dmdirc.ktirc.model.*
7 7
 import com.dmdirc.ktirc.util.currentTimeProvider
8
+import com.dmdirc.ktirc.util.logger
8 9
 import io.ktor.util.KtorExperimentalAPI
9 10
 import kotlinx.coroutines.*
10 11
 import kotlinx.coroutines.channels.map
@@ -90,6 +91,8 @@ interface IrcClient {
90 91
 @ExperimentalCoroutinesApi
91 92
 class IrcClientImpl(private val server: Server, override val profile: Profile) : IrcClient, CoroutineScope {
92 93
 
94
+    private val log by logger()
95
+
93 96
     override val coroutineContext = GlobalScope.newCoroutineContext(Dispatchers.IO)
94 97
 
95 98
     internal var socketFactory: (CoroutineScope, String, Int, Boolean) -> LineBufferedSocket = ::KtorLineBufferedSocket
@@ -106,7 +109,7 @@ class IrcClientImpl(private val server: Server, override val profile: Profile) :
106 109
     private val connecting = AtomicBoolean(false)
107 110
 
108 111
     override fun send(message: String) {
109
-        socket?.sendChannel?.offer(message.toByteArray())
112
+        socket?.sendChannel?.offer(message.toByteArray()) ?: log.warning { "No send channel for message: $message"}
110 113
     }
111 114
 
112 115
     override fun connect() {
@@ -127,6 +130,7 @@ class IrcClientImpl(private val server: Server, override val profile: Profile) :
127 130
                 // TODO: Send correct host
128 131
                 sendUser(profile.userName, profile.realName)
129 132
                 messageHandler.processMessages(this@IrcClientImpl, receiveChannel.map { parser.parse(it) })
133
+                reset()
130 134
                 emitEvent(ServerDisconnected(currentTimeProvider()))
131 135
             }
132 136
         }
@@ -148,4 +152,12 @@ class IrcClientImpl(private val server: Server, override val profile: Profile) :
148 152
     private fun emitEvent(event: IrcEvent) = messageHandler.emitEvent(this, event)
149 153
     private fun sendPasswordIfPresent() = server.password?.let(this::sendPassword)
150 154
 
155
+    internal fun reset() {
156
+        serverState.reset()
157
+        channelState.clear()
158
+        userState.reset()
159
+        socket = null
160
+        connecting.set(false)
161
+    }
162
+
151 163
 }

+ 6
- 0
src/main/kotlin/com/dmdirc/ktirc/model/CapabilitiesState.kt View File

@@ -15,6 +15,12 @@ class CapabilitiesState {
15 15
     /** The capabilities that we have agreed to enable. */
16 16
     val enabledCapabilities = HashMap<Capability, String>()
17 17
 
18
+    internal fun reset() {
19
+        negotiationState = CapabilitiesNegotiationState.AWAITING_LIST
20
+        advertisedCapabilities.clear()
21
+        enabledCapabilities.clear()
22
+    }
23
+
18 24
 }
19 25
 
20 26
 /**

+ 7
- 0
src/main/kotlin/com/dmdirc/ktirc/model/ChannelState.kt View File

@@ -30,6 +30,13 @@ class ChannelState(val name: String, caseMappingProvider: () -> CaseMapping) {
30 30
      * If [modesDiscovered] is false, this map may be missing modes that the server hasn't told us about.
31 31
      */
32 32
     var modes = HashMap<Char, String>()
33
+
34
+    internal fun reset() {
35
+        receivingUserList = false
36
+        modesDiscovered = false
37
+        users.clear()
38
+        modes.clear()
39
+    }
33 40
 }
34 41
 
35 42
 /**

+ 6
- 2
src/main/kotlin/com/dmdirc/ktirc/model/SaslState.kt View File

@@ -14,12 +14,16 @@ internal class SaslState(private val mechanisms: Collection<SaslMechanism>) {
14 14
     var mechanismState: Any? = null
15 15
 
16 16
     fun getPreferredSaslMechanism(serverMechanisms: String?): SaslMechanism? {
17
-        serverMechanisms ?: return null
18
-        val serverSupported = serverMechanisms.split(',')
17
+        val serverSupported = serverMechanisms?.split(',') ?: return null
19 18
         return mechanisms
20 19
                 .filter { it.priority < currentMechanism?.priority ?: Int.MAX_VALUE }
21 20
                 .filter { serverMechanisms.isEmpty() || it.ircName in serverSupported }
22 21
                 .maxBy { it.priority }
23 22
     }
24 23
 
24
+    fun reset() {
25
+        saslBuffer = ""
26
+        currentMechanism = null
27
+    }
28
+
25 29
 }

+ 18
- 6
src/main/kotlin/com/dmdirc/ktirc/model/ServerState.kt View File

@@ -10,8 +10,8 @@ import kotlin.reflect.KClass
10 10
  * Contains the current state of a single IRC server.
11 11
  */
12 12
 class ServerState internal constructor(
13
-        initialNickname: String,
14
-        initialServerName: String,
13
+        private val initialNickname: String,
14
+        private val initialServerName: String,
15 15
         saslMechanisms: Collection<SaslMechanism> = supportedSaslMechanisms) {
16 16
 
17 17
     private val log by logger()
@@ -79,6 +79,16 @@ class ServerState internal constructor(
79 79
         return ChannelModeType.NoParameter
80 80
     }
81 81
 
82
+    internal fun reset() {
83
+        receivedWelcome = false
84
+        status = ServerStatus.Disconnected
85
+        localNickname = initialNickname
86
+        serverName = initialServerName
87
+        features.clear()
88
+        capabilities.reset()
89
+        sasl.reset()
90
+    }
91
+
82 92
 }
83 93
 
84 94
 /**
@@ -104,6 +114,8 @@ class ServerFeatureMap {
104 114
 
105 115
     internal fun setAll(featureMap: ServerFeatureMap) = featureMap.features.forEach { feature, value -> features[feature] = value }
106 116
     internal fun reset(feature: ServerFeature<*>) = features.put(feature, null)
117
+    internal fun clear() = features.clear()
118
+    internal fun isEmpty() = features.isEmpty()
107 119
 
108 120
 }
109 121
 
@@ -143,10 +155,6 @@ sealed class ServerFeature<T : Any>(val name: String, val type: KClass<T>, val d
143 155
     object WhoxSupport : ServerFeature<Boolean>("WHOX", Boolean::class, false)
144 156
 }
145 157
 
146
-internal val serverFeatures: Map<String, ServerFeature<*>> by lazy {
147
-    ServerFeature::class.nestedClasses.map { it.objectInstance as ServerFeature<*> }.associateBy { it.name }
148
-}
149
-
150 158
 /**
151 159
  * Enumeration of the possible states of a server.
152 160
  */
@@ -160,3 +168,7 @@ enum class ServerStatus {
160 168
     /** We are connected and commands can be sent. */
161 169
     Ready,
162 170
 }
171
+
172
+internal val serverFeatures: Map<String, ServerFeature<*>> by lazy {
173
+    ServerFeature::class.nestedClasses.map { it.objectInstance as ServerFeature<*> }.associateBy { it.name }
174
+}

+ 4
- 0
src/main/kotlin/com/dmdirc/ktirc/model/UserState.kt View File

@@ -35,6 +35,10 @@ class UserState(private val caseMappingProvider: () -> CaseMapping): Iterable<Kn
35 35
         }
36 36
     }
37 37
 
38
+    internal fun reset() {
39
+        users.clear()
40
+    }
41
+
38 42
 }
39 43
 
40 44
 /**

+ 29
- 33
src/test/kotlin/com/dmdirc/ktirc/IrcClientTest.kt View File

@@ -49,7 +49,7 @@ internal class IrcClientImplTest {
49 49
     }
50 50
 
51 51
     @Test
52
-    fun `IrcClientImpl uses socket factory to create a new socket on connect`() {
52
+    fun `uses socket factory to create a new socket on connect`() {
53 53
         val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
54 54
         client.socketFactory = mockSocketFactory
55 55
         client.connect()
@@ -58,7 +58,7 @@ internal class IrcClientImplTest {
58 58
     }
59 59
 
60 60
     @Test
61
-    fun `IrcClientImpl uses socket factory to create a new tls on connect`() {
61
+    fun `uses socket factory to create a new tls on connect`() {
62 62
         val client = IrcClientImpl(Server(HOST, PORT, true), Profile(NICK, REAL_NAME, USER_NAME))
63 63
         client.socketFactory = mockSocketFactory
64 64
         client.connect()
@@ -67,7 +67,7 @@ internal class IrcClientImplTest {
67 67
     }
68 68
 
69 69
     @Test
70
-    fun `IrcClientImpl throws if socket already exists`() {
70
+    fun `throws if socket already exists`() {
71 71
         val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
72 72
         client.socketFactory = mockSocketFactory
73 73
         client.connect()
@@ -78,7 +78,7 @@ internal class IrcClientImplTest {
78 78
     }
79 79
 
80 80
     @Test
81
-    fun `IrcClientImpl emits connection events with local time`() = runBlocking {
81
+    fun `emits connection events with local time`() = runBlocking {
82 82
         currentTimeProvider = { TestConstants.time }
83 83
         val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
84 84
         client.socketFactory = mockSocketFactory
@@ -96,48 +96,43 @@ internal class IrcClientImplTest {
96 96
     }
97 97
 
98 98
     @Test
99
-    fun `IrcClientImpl emits disconnected event with local time when read channel closed`() = runBlocking {
99
+    fun `emits disconnected event with local time when read channel closed`() = runBlocking {
100 100
         currentTimeProvider = { TestConstants.time }
101 101
         val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
102 102
         client.socketFactory = mockSocketFactory
103 103
         client.connect()
104
-        client.blockUntilConnected()
105 104
 
106 105
         client.onEvent(mockEventHandler)
107 106
         readLineChannel.close()
108 107
 
109 108
         val captor = argumentCaptor<ServerDisconnected>()
110
-        verify(mockEventHandler, timeout(500)).invoke(captor.capture())
109
+        verify(mockEventHandler, timeout(500).atLeast(2)).invoke(captor.capture())
111 110
         assertEquals(TestConstants.time, captor.lastValue.time)
112 111
     }
113 112
 
114 113
     @Test
115
-    fun `IrcClientImpl sends basic connection strings`() = runBlocking {
114
+    fun `sends basic connection strings`() = runBlocking {
116 115
         val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
117 116
         client.socketFactory = mockSocketFactory
118 117
         client.connect()
119 118
 
120
-        client.blockUntilConnected()
121
-
122 119
         assertEquals("CAP LS 302", String(sendLineChannel.receive()))
123 120
         assertEquals("NICK :$NICK", String(sendLineChannel.receive()))
124 121
         assertEquals("USER $USER_NAME 0 * :$REAL_NAME", String(sendLineChannel.receive()))
125 122
     }
126 123
 
127 124
     @Test
128
-    fun `IrcClientImpl sends password first, when present`() = runBlocking {
125
+    fun `sends password first, when present`() = runBlocking {
129 126
         val client = IrcClientImpl(Server(HOST, PORT, password = PASSWORD), Profile(NICK, REAL_NAME, USER_NAME))
130 127
         client.socketFactory = mockSocketFactory
131 128
         client.connect()
132 129
 
133
-        client.blockUntilConnected()
134
-
135 130
         assertEquals("CAP LS 302", String(sendLineChannel.receive()))
136 131
         assertEquals("PASS :$PASSWORD", String(sendLineChannel.receive()))
137 132
     }
138 133
 
139 134
     @Test
140
-    fun `IrcClientImpl sends events to provided event handler`() {
135
+    fun `sends events to provided event handler`() {
141 136
         val client = IrcClientImpl(Server(HOST, PORT, password = PASSWORD), Profile(NICK, REAL_NAME, USER_NAME))
142 137
         client.socketFactory = mockSocketFactory
143 138
         client.onEvent(mockEventHandler)
@@ -152,14 +147,14 @@ internal class IrcClientImplTest {
152 147
     }
153 148
 
154 149
     @Test
155
-    fun `IrcClient gets case mapping from server features`() {
150
+    fun `gets case mapping from server features`() {
156 151
         val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
157 152
         client.serverState.features[ServerFeature.ServerCaseMapping] = CaseMapping.RfcStrict
158 153
         assertEquals(CaseMapping.RfcStrict, client.caseMapping)
159 154
     }
160 155
 
161 156
     @Test
162
-    fun `IrcClient indicates if user is local user or not`() {
157
+    fun `indicates if user is local user or not`() {
163 158
         val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
164 159
         client.serverState.localNickname = "[acidBurn]"
165 160
 
@@ -168,7 +163,7 @@ internal class IrcClientImplTest {
168 163
     }
169 164
 
170 165
     @Test
171
-    fun `IrcClient indicates if nickname is local user or not`() {
166
+    fun `indicates if nickname is local user or not`() {
172 167
         val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
173 168
         client.serverState.localNickname = "[acidBurn]"
174 169
 
@@ -177,7 +172,7 @@ internal class IrcClientImplTest {
177 172
     }
178 173
 
179 174
     @Test
180
-    fun `IrcClient uses current case mapping to check local user`() {
175
+    fun `uses current case mapping to check local user`() {
181 176
         val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
182 177
         client.serverState.localNickname = "[acidBurn]"
183 178
         client.serverState.features[ServerFeature.ServerCaseMapping] = CaseMapping.Ascii
@@ -185,13 +180,11 @@ internal class IrcClientImplTest {
185 180
     }
186 181
 
187 182
     @Test
188
-    fun `IrcClientImpl sends text to socket`() = runBlocking {
183
+    fun `sends text to socket`() = runBlocking {
189 184
         val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
190 185
         client.socketFactory = mockSocketFactory
191 186
         client.connect()
192 187
 
193
-        client.blockUntilConnected()
194
-
195 188
         client.send("testing 123")
196 189
 
197 190
         assertEquals(true, withTimeoutOrNull(500) {
@@ -207,26 +200,22 @@ internal class IrcClientImplTest {
207 200
     }
208 201
 
209 202
     @Test
210
-    fun `IrcClientImpl disconnects the socket`() = runBlocking {
203
+    fun `disconnects the socket`() = runBlocking {
211 204
         val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
212 205
         client.socketFactory = mockSocketFactory
213 206
         client.connect()
214 207
 
215
-        client.blockUntilConnected()
216
-
217 208
         client.disconnect()
218 209
 
219 210
         verify(mockSocket, timeout(500)).disconnect()
220 211
     }
221 212
 
222 213
     @Test
223
-    fun `IrcClientImpl sends messages in order`() = runBlocking {
214
+    fun `sends messages in order`() = runBlocking {
224 215
         val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
225 216
         client.socketFactory = mockSocketFactory
226 217
         client.connect()
227 218
 
228
-        client.blockUntilConnected()
229
-
230 219
         (0..100).forEach { client.send("TEST $it") }
231 220
 
232 221
         assertEquals(100, withTimeoutOrNull(500) {
@@ -242,21 +231,28 @@ internal class IrcClientImplTest {
242 231
     }
243 232
 
244 233
     @Test
245
-    fun `IrcClientImpl defaults local nickname to profile`() = runBlocking {
234
+    fun `defaults local nickname to profile`() = runBlocking {
246 235
         val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
247 236
         assertEquals(NICK, client.serverState.localNickname)
248 237
     }
249 238
 
250 239
     @Test
251
-    fun `IrcClientImpl defaults server name to host name`() = runBlocking {
240
+    fun `defaults server name to host name`() = runBlocking {
252 241
         val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
253 242
         assertEquals(HOST, client.serverState.serverName)
254 243
     }
255 244
 
256
-    private suspend fun IrcClientImpl.blockUntilConnected() {
257
-        // Yuck. Maybe connect should be asynchronous?
258
-        while (serverState.status <= ServerStatus.Connecting) {
259
-            delay(50)
245
+    @Test
246
+    fun `reset clears all state`() {
247
+        with (IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))) {
248
+            userState += User("acidBurn")
249
+            channelState += ChannelState("#thegibson") { CaseMapping.Rfc }
250
+            serverState.serverName = "root.$HOST"
251
+            reset()
252
+
253
+            assertEquals(0, userState.count())
254
+            assertEquals(0, channelState.count())
255
+            assertEquals(HOST, serverState.serverName)
260 256
         }
261 257
     }
262 258
 

+ 16
- 4
src/test/kotlin/com/dmdirc/ktirc/model/CapabilitiesStateTest.kt View File

@@ -1,15 +1,27 @@
1 1
 package com.dmdirc.ktirc.model
2 2
 
3 3
 import org.junit.jupiter.api.Assertions.assertEquals
4
+import org.junit.jupiter.api.Assertions.assertTrue
4 5
 import org.junit.jupiter.api.Test
5 6
 
6 7
 internal class CapabilitiesStateTest {
7 8
 
8 9
     @Test
9
-    fun `defaults negotiation state to awaiting list`() {
10
-        val capabilitiesState = CapabilitiesState()
10
+    fun `defaults negotiation state to awaiting list`() = with(CapabilitiesState()) {
11
+        assertEquals(CapabilitiesNegotiationState.AWAITING_LIST, negotiationState)
12
+    }
13
+
14
+    @Test
15
+    fun `reset clears all state`() = with(CapabilitiesState()) {
16
+        advertisedCapabilities[Capability.SaslAuthentication] = "foo"
17
+        enabledCapabilities[Capability.SaslAuthentication] = "foo"
18
+        negotiationState = CapabilitiesNegotiationState.FINISHED
19
+
20
+        reset()
11 21
 
12
-        assertEquals(CapabilitiesNegotiationState.AWAITING_LIST, capabilitiesState.negotiationState)
22
+        assertTrue(advertisedCapabilities.isEmpty())
23
+        assertTrue(enabledCapabilities.isEmpty())
24
+        assertEquals(CapabilitiesNegotiationState.AWAITING_LIST, negotiationState)
13 25
     }
14 26
 
15
-}
27
+}

+ 17
- 2
src/test/kotlin/com/dmdirc/ktirc/model/ChannelStateTest.kt View File

@@ -1,7 +1,7 @@
1 1
 package com.dmdirc.ktirc.model
2 2
 
3
-import org.junit.jupiter.api.Assertions.assertFalse
4
-import org.junit.jupiter.api.Assertions.assertTrue
3
+import com.dmdirc.ktirc.io.CaseMapping
4
+import org.junit.jupiter.api.Assertions.*
5 5
 import org.junit.jupiter.api.Test
6 6
 
7 7
 internal class ChannelStateTest {
@@ -22,4 +22,19 @@ internal class ChannelStateTest {
22 22
         assertFalse(ChannelModeType.NoParameter.needsParameterToUnset)
23 23
     }
24 24
 
25
+    @Test
26
+    fun `reset resets all state`() = with(ChannelState("#thegibson") { CaseMapping.Rfc }) {
27
+        receivingUserList = true
28
+        modesDiscovered = true
29
+        modes['a'] = "b"
30
+        users += ChannelUser("acidBurn")
31
+
32
+        reset()
33
+
34
+        assertFalse(receivingUserList)
35
+        assertFalse(modesDiscovered)
36
+        assertTrue(modes.isEmpty())
37
+        assertEquals(0, users.count())
38
+    }
39
+
25 40
 }

+ 14
- 2
src/test/kotlin/com/dmdirc/ktirc/model/SaslStateTest.kt View File

@@ -3,8 +3,7 @@ package com.dmdirc.ktirc.model
3 3
 import com.dmdirc.ktirc.sasl.SaslMechanism
4 4
 import com.nhaarman.mockitokotlin2.doReturn
5 5
 import com.nhaarman.mockitokotlin2.mock
6
-import org.junit.jupiter.api.Assertions.assertEquals
7
-import org.junit.jupiter.api.Assertions.assertNull
6
+import org.junit.jupiter.api.Assertions.*
8 7
 import org.junit.jupiter.api.Test
9 8
 
10 9
 internal class SaslStateTest {
@@ -78,4 +77,17 @@ internal class SaslStateTest {
78 77
         assertNull(state.mechanismState)
79 78
     }
80 79
 
80
+    @Test
81
+    fun `reset clears all state`() = with(SaslState(mechanisms)) {
82
+        currentMechanism = mech2
83
+        mechanismState = "in progress"
84
+        saslBuffer = "abcdef"
85
+
86
+        reset()
87
+
88
+        assertNull(currentMechanism)
89
+        assertNull(mechanismState)
90
+        assertTrue(saslBuffer.isEmpty())
91
+    }
92
+
81 93
 }

+ 25
- 9
src/test/kotlin/com/dmdirc/ktirc/model/ServerFeatureMapTest.kt View File

@@ -7,26 +7,26 @@ import org.junit.jupiter.api.Test
7 7
 internal class ServerFeatureMapTest {
8 8
 
9 9
     @Test
10
-    fun `ServerFeatureMap should return defaults for unspecified features`() {
10
+    fun `should return defaults for unspecified features`() {
11 11
         val featureMap = ServerFeatureMap()
12 12
         assertEquals(200, featureMap[ServerFeature.MaximumChannelNameLength])
13 13
     }
14 14
 
15 15
     @Test
16
-    fun `ServerFeatureMap should return null for unspecified features with no default`() {
16
+    fun `should return null for unspecified features with no default`() {
17 17
         val featureMap = ServerFeatureMap()
18 18
         assertNull(featureMap[ServerFeature.ChannelModes])
19 19
     }
20 20
 
21 21
     @Test
22
-    fun `ServerFeatureMap should return previously set value for features`() {
22
+    fun `should return previously set value for features`() {
23 23
         val featureMap = ServerFeatureMap()
24 24
         featureMap[ServerFeature.MaximumChannels] = 123
25 25
         assertEquals(123, featureMap[ServerFeature.MaximumChannels])
26 26
     }
27 27
 
28 28
     @Test
29
-    fun `ServerFeatureMap should return default set value for features that were reset`() {
29
+    fun `should return default set value for features that were reset`() {
30 30
         val featureMap = ServerFeatureMap()
31 31
         featureMap[ServerFeature.MaximumChannels] = 123
32 32
         featureMap.reset(ServerFeature.MaximumChannels)
@@ -34,7 +34,7 @@ internal class ServerFeatureMapTest {
34 34
     }
35 35
 
36 36
     @Test
37
-    fun `ServerFeatureMap should throw if a feature is set with the wrong type`() {
37
+    fun `should throw if a feature is set with the wrong type`() {
38 38
         val featureMap = ServerFeatureMap()
39 39
         assertThrows(IllegalArgumentException::class.java) {
40 40
             featureMap[ServerFeature.MaximumChannels] = "123"
@@ -42,7 +42,7 @@ internal class ServerFeatureMapTest {
42 42
     }
43 43
 
44 44
     @Test
45
-    fun `ServerFeatureMap sets all features from another map`() {
45
+    fun `sets all features from another map`() {
46 46
         val featureMap1 = ServerFeatureMap()
47 47
         val featureMap2 = ServerFeatureMap()
48 48
         featureMap2[ServerFeature.WhoxSupport] = true
@@ -53,9 +53,8 @@ internal class ServerFeatureMapTest {
53 53
         assertArrayEquals(arrayOf("abc", "def"), featureMap1[ServerFeature.ChannelModes])
54 54
     }
55 55
 
56
-
57 56
     @Test
58
-    fun `ServerFeatureMap resets features reset in another map`() {
57
+    fun `resets features reset in another map`() {
59 58
         val featureMap1 = ServerFeatureMap()
60 59
         val featureMap2 = ServerFeatureMap()
61 60
         featureMap1[ServerFeature.ServerCaseMapping] = CaseMapping.RfcStrict
@@ -64,5 +63,22 @@ internal class ServerFeatureMapTest {
64 63
 
65 64
         assertEquals(CaseMapping.Rfc, featureMap1[ServerFeature.ServerCaseMapping])
66 65
     }
66
+
67
+    @Test
68
+    fun `clear removes all features`() {
69
+        val featureMap = ServerFeatureMap()
70
+        featureMap[ServerFeature.MaximumChannels] = 123
71
+        featureMap[ServerFeature.Network] = "testnet"
72
+        featureMap.clear()
73
+        assertTrue(featureMap.isEmpty())
74
+    }
75
+
76
+    @Test
77
+    fun `isEmpty returns true if empty`() {
78
+        val featureMap = ServerFeatureMap()
79
+        assertTrue(featureMap.isEmpty())
80
+        featureMap[ServerFeature.MaximumChannels] = 123
81
+        assertFalse(featureMap.isEmpty())
82
+    }
67 83
     
68
-}
84
+}

+ 21
- 0
src/test/kotlin/com/dmdirc/ktirc/model/ServerStateTest.kt View File

@@ -58,6 +58,27 @@ internal class ServerStateTest {
58 58
         assertEquals(ChannelModeType.NoParameter, serverState.channelModeType('b'))
59 59
     }
60 60
 
61
+    @Test
62
+    fun `reset clears all state`() = with(ServerState("acidBurn", "")) {
63
+        receivedWelcome = true
64
+        status = ServerStatus.Connecting
65
+        localNickname = "acidBurn3"
66
+        serverName = "root.the.gibson"
67
+        features[ServerFeature.Network] = "gibson"
68
+        capabilities.advertisedCapabilities[Capability.SaslAuthentication] = "sure"
69
+        sasl.saslBuffer = "in progress"
70
+
71
+        reset()
72
+
73
+        assertFalse(receivedWelcome)
74
+        assertEquals(ServerStatus.Disconnected, status)
75
+        assertEquals("acidBurn", localNickname)
76
+        assertEquals("", serverName)
77
+        assertTrue(features.isEmpty())
78
+        assertTrue(capabilities.advertisedCapabilities.isEmpty())
79
+        assertEquals("", sasl.saslBuffer)
80
+    }
81
+
61 82
 }
62 83
 
63 84
 internal class ModePrefixMappingTest {

+ 16
- 5
src/test/kotlin/com/dmdirc/ktirc/model/UserStateTest.kt View File

@@ -9,7 +9,7 @@ internal class UserStateTest {
9 9
     private val userState = UserState { CaseMapping.Rfc }
10 10
 
11 11
     @Test
12
-    fun `UserState adds and gets new users`() {
12
+    fun `adds and gets new users`() {
13 13
         userState += User("acidBurn", "libby", "root.localhost")
14 14
         val user = userState["acidburn"]
15 15
         assertNotNull(user)
@@ -19,21 +19,21 @@ internal class UserStateTest {
19 19
     }
20 20
 
21 21
     @Test
22
-    fun `UserState removes users`() {
22
+    fun `removes users`() {
23 23
         userState += User("acidBurn", "libby", "root.localhost")
24 24
         userState -= User("ACIDBURN")
25 25
         assertNull(userState["acidburn"])
26 26
     }
27 27
 
28 28
     @Test
29
-    fun `UserState removes users by nickname`() {
29
+    fun `removes users by nickname`() {
30 30
         userState += User("acidBurn", "libby", "root.localhost")
31 31
         userState -= "ACIDBURN"
32 32
         assertNull(userState["acidburn"])
33 33
     }
34 34
 
35 35
     @Test
36
-    fun `UserState updates existing user with same nickname`() {
36
+    fun `updates existing user with same nickname`() {
37 37
         userState += User("acidBurn", "libby", "root.localhost")
38 38
         userState.update(User("acidBurn", realName = "Libby", awayMessage = "Hacking"))
39 39
 
@@ -46,7 +46,7 @@ internal class UserStateTest {
46 46
     }
47 47
 
48 48
     @Test
49
-    fun `UserState updates existing user with new nickname`() {
49
+    fun `updates existing user with new nickname`() {
50 50
         userState += User("acidBurn", "libby", "root.localhost")
51 51
         userState.update(User("acidBurn2", realName = "Libby", awayMessage = "Hacking"), "acidBurn")
52 52
 
@@ -95,4 +95,15 @@ internal class UserStateTest {
95 95
         assertNotNull(userState["zeroCool"])
96 96
     }
97 97
 
98
+    @Test
99
+    fun `reset clears all state`() {
100
+        userState += User("acidBurn", "libby", "root.localhost")
101
+        userState += User("zeroCool", "dade", "root.localhost")
102
+        userState += User("acidBurn2", "libby", "root.localhost")
103
+
104
+        userState.reset()
105
+
106
+        assertEquals(0, userState.count())
107
+    }
108
+
98 109
 }

Loading…
Cancel
Save