Browse Source

Introduce event handlers, refactor message processors.

tags/v0.1.0
Chris Smith 5 years ago
parent
commit
f2e081e6c7

+ 88
- 0
src/main/kotlin/com/dmdirc/ktirc/IrcClient.kt View File

@@ -0,0 +1,88 @@
1
+package com.dmdirc.ktirc
2
+
3
+import com.dmdirc.ktirc.events.EventHandler
4
+import com.dmdirc.ktirc.events.IrcEvent
5
+import com.dmdirc.ktirc.events.ServerWelcome
6
+import com.dmdirc.ktirc.events.eventHandlers
7
+import com.dmdirc.ktirc.io.KtorLineBufferedSocket
8
+import com.dmdirc.ktirc.io.LineBufferedSocket
9
+import com.dmdirc.ktirc.io.MessageHandler
10
+import com.dmdirc.ktirc.io.MessageParser
11
+import com.dmdirc.ktirc.messages.*
12
+import com.dmdirc.ktirc.model.Profile
13
+import com.dmdirc.ktirc.model.Server
14
+import com.dmdirc.ktirc.model.ServerState
15
+import kotlinx.coroutines.channels.map
16
+import kotlinx.coroutines.coroutineScope
17
+import kotlinx.coroutines.runBlocking
18
+import java.util.logging.Level
19
+import java.util.logging.LogManager
20
+
21
+
22
+interface IrcClient {
23
+
24
+    suspend fun send(message: String)
25
+
26
+    val serverState: ServerState
27
+
28
+}
29
+
30
+// TODO: How should alternative nicknames work?
31
+// TODO: Should IRC Client take a pool of servers and rotate through, or make the caller do that?
32
+// TODO: Should there be a default profile?
33
+class IrcClientImpl(private val server: Server, private val profile: Profile) : IrcClient {
34
+
35
+    var socketFactory: (String, Int) -> LineBufferedSocket = ::KtorLineBufferedSocket
36
+
37
+    override val serverState = ServerState(profile.initialNick)
38
+
39
+    private val messageHandler = MessageHandler(messageProcessors, eventHandlers + object : EventHandler {
40
+        override suspend fun processEvent(client: IrcClient, event: IrcEvent) {
41
+            when (event) {
42
+                is ServerWelcome -> client.send(joinMessage("#mdbot"))
43
+            }
44
+        }
45
+    })
46
+
47
+    private val parser = MessageParser()
48
+    private var socket: LineBufferedSocket? = null
49
+
50
+    override suspend fun send(message: String) {
51
+        socket?.sendLine(message)
52
+    }
53
+
54
+    suspend fun connect() {
55
+        // TODO: Concurrency!
56
+        check(socket == null)
57
+        coroutineScope {
58
+            with(socketFactory(server.host, server.port)) {
59
+                socket = this
60
+                connect()
61
+                // TODO: CAP LS
62
+                server.password?.let { pass -> sendLine(passwordMessage(pass)) }
63
+                sendLine(nickMessage(profile.initialNick))
64
+                // TODO: Send correct host
65
+                sendLine(userMessage(profile.userName, "localhost", server.host, profile.realName))
66
+                // TODO: This should be elsewhere
67
+                messageHandler.processMessages(this@IrcClientImpl, readLines(this@coroutineScope).map { parser.parse(it) })
68
+            }
69
+        }
70
+    }
71
+
72
+    companion object {
73
+        @JvmStatic
74
+        fun main(args: Array<String>) {
75
+            val rootLogger = LogManager.getLogManager().getLogger("")
76
+            rootLogger.level = Level.FINEST
77
+            for (h in rootLogger.handlers) {
78
+                h.level = Level.FINEST
79
+            }
80
+
81
+            runBlocking {
82
+                val client = IrcClientImpl(Server("irc.quakenet.org", 6667), Profile("KtIrc", "Kotlin!", "kotlin"))
83
+                client.connect()
84
+            }
85
+        }
86
+    }
87
+
88
+}

+ 14
- 0
src/main/kotlin/com/dmdirc/ktirc/events/EventHandler.kt View File

@@ -0,0 +1,14 @@
1
+package com.dmdirc.ktirc.events
2
+
3
+import com.dmdirc.ktirc.IrcClient
4
+
5
+interface EventHandler {
6
+
7
+    suspend fun processEvent(client: IrcClient, event: IrcEvent)
8
+
9
+}
10
+
11
+val eventHandlers = setOf(
12
+        PingHandler(),
13
+        ServerStateHandler()
14
+)

+ 21
- 0
src/main/kotlin/com/dmdirc/ktirc/events/Events.kt View File

@@ -2,6 +2,27 @@
2 2
 
3 3
 package com.dmdirc.ktirc.events
4 4
 
5
+import com.dmdirc.ktirc.model.ServerFeatureMap
6
+
5 7
 sealed class IrcEvent
8
+
9
+/**
10
+ * Raised when the server initially welcomes us to the IRC network.
11
+ */
12
+data class ServerWelcome(val localNick: String): IrcEvent()
13
+
14
+/**
15
+ * Raised when the features supported by the server have changed. This may occur numerous times during the
16
+ * connection phase.
17
+ */
18
+data class ServerFeaturesUpdated(val serverFeatures: ServerFeatureMap) : IrcEvent()
19
+
20
+/**
21
+ * Raised when the connection to the server has been established, configuration information has been received, etc.
22
+ */
6 23
 object ServerConnected : IrcEvent()
24
+
25
+/**
26
+ * Raised whenever a PING is received from the server.
27
+ */
7 28
 data class PingReceived(val nonce: ByteArray): IrcEvent()

+ 14
- 0
src/main/kotlin/com/dmdirc/ktirc/events/PingHandler.kt View File

@@ -0,0 +1,14 @@
1
+package com.dmdirc.ktirc.events
2
+
3
+import com.dmdirc.ktirc.IrcClient
4
+import com.dmdirc.ktirc.messages.pongMessage
5
+
6
+class PingHandler : EventHandler {
7
+
8
+    override suspend fun processEvent(client: IrcClient, event: IrcEvent) {
9
+        when (event) {
10
+            is PingReceived -> client.send(pongMessage(event.nonce))
11
+        }
12
+    }
13
+
14
+}

+ 14
- 0
src/main/kotlin/com/dmdirc/ktirc/events/ServerStateHandler.kt View File

@@ -0,0 +1,14 @@
1
+package com.dmdirc.ktirc.events
2
+
3
+import com.dmdirc.ktirc.IrcClient
4
+
5
+class ServerStateHandler : EventHandler {
6
+
7
+    override suspend fun processEvent(client: IrcClient, event: IrcEvent) {
8
+        when (event) {
9
+            is ServerWelcome -> client.serverState.localNickname = event.localNick
10
+            is ServerFeaturesUpdated -> client.serverState.features.setAll(event.serverFeatures)
11
+        }
12
+    }
13
+
14
+}

+ 11
- 4
src/main/kotlin/com/dmdirc/ktirc/io/MessageHandler.kt View File

@@ -1,17 +1,24 @@
1 1
 package com.dmdirc.ktirc.io
2 2
 
3
-import com.dmdirc.ktirc.events.IrcEvent
3
+import com.dmdirc.ktirc.IrcClient
4
+import com.dmdirc.ktirc.events.EventHandler
4 5
 import com.dmdirc.ktirc.messages.MessageProcessor
5 6
 import com.dmdirc.ktirc.util.logger
6 7
 import kotlinx.coroutines.channels.ReceiveChannel
7 8
 import kotlinx.coroutines.channels.consumeEach
8 9
 
9
-class MessageHandler(private val processors: Collection<MessageProcessor>, private val eventHandler: (IrcEvent) -> Unit) {
10
+class MessageHandler(private val processors: Collection<MessageProcessor>, private val handlers: Collection<EventHandler>) {
10 11
 
11 12
     private val log by logger()
12 13
 
13
-    suspend fun processMessages(messages: ReceiveChannel<IrcMessage>) {
14
-        messages.consumeEach { it.process().forEach(eventHandler) }
14
+    suspend fun processMessages(ircClient: IrcClient, messages: ReceiveChannel<IrcMessage>) {
15
+        messages.consumeEach {
16
+            it.process().forEach { event ->
17
+                handlers.forEach { handler ->
18
+                    handler.processEvent(ircClient, event)
19
+                }
20
+            }
21
+        }
15 22
     }
16 23
 
17 24
     private fun IrcMessage.process() = this.getProcessor()?.process(this) ?: emptyList()

+ 13
- 14
src/main/kotlin/com/dmdirc/ktirc/messages/ISupportProcessor.kt View File

@@ -1,28 +1,27 @@
1 1
 package com.dmdirc.ktirc.messages
2 2
 
3
-import com.dmdirc.ktirc.events.IrcEvent
3
+import com.dmdirc.ktirc.events.ServerFeaturesUpdated
4 4
 import com.dmdirc.ktirc.io.CaseMapping
5 5
 import com.dmdirc.ktirc.io.IrcMessage
6
-import com.dmdirc.ktirc.state.ServerState
7
-import com.dmdirc.ktirc.state.serverFeatures
6
+import com.dmdirc.ktirc.model.ServerFeatureMap
7
+import com.dmdirc.ktirc.model.serverFeatures
8 8
 import com.dmdirc.ktirc.util.logger
9 9
 import kotlin.reflect.KClass
10 10
 
11
-class ISupportProcessor(val serverState: ServerState) : MessageProcessor {
11
+class ISupportProcessor : MessageProcessor {
12 12
 
13 13
     private val log by logger()
14 14
 
15 15
     override val commands = arrayOf("005")
16 16
 
17
-    override fun process(message: IrcMessage): List<IrcEvent> {
17
+    override fun process(message: IrcMessage) = listOf(ServerFeaturesUpdated(ServerFeatureMap().apply {
18 18
         // Ignore the first (nickname) and last ("are supported by this server") params
19 19
         for (i in 1 until message.params.size - 1) {
20 20
             parseParam(message.params[i])
21 21
         }
22
-        return emptyList()
23
-    }
22
+    }))
24 23
 
25
-    private fun parseParam(param: ByteArray) = when (param[0]) {
24
+    private fun ServerFeatureMap.parseParam(param: ByteArray) = when (param[0]) {
26 25
         '-'.toByte() -> resetFeature(param.sliceArray(1 until param.size))
27 26
         else -> when (val equals = param.indexOf('='.toByte())) {
28 27
             -1 -> enableFeatureWithDefault(param)
@@ -30,23 +29,23 @@ class ISupportProcessor(val serverState: ServerState) : MessageProcessor {
30 29
         }
31 30
     }
32 31
 
33
-    private fun resetFeature(name: ByteArray) = name.asFeature()?.let {
34
-        serverState.resetFeature(it)
32
+    private fun ServerFeatureMap.resetFeature(name: ByteArray) = name.asFeature()?.let {
33
+        reset(it)
35 34
         log.finer { "Reset feature ${it::class}" }
36 35
     }
37 36
 
38 37
     @Suppress("UNCHECKED_CAST")
39
-    private fun enableFeature(name: ByteArray, value: ByteArray) {
38
+    private fun ServerFeatureMap.enableFeature(name: ByteArray, value: ByteArray) {
40 39
         name.asFeature()?.let { feature ->
41
-            serverState.setFeature(feature, value.cast(feature.type))
40
+            set(feature, value.cast(feature.type))
42 41
             log.finer { "Set feature ${feature::class} to ${String(value)}" }
43 42
         }
44 43
     }
45 44
 
46
-    private fun enableFeatureWithDefault(name: ByteArray) {
45
+    private fun ServerFeatureMap.enableFeatureWithDefault(name: ByteArray) {
47 46
         name.asFeature()?.let { feature ->
48 47
             when (feature.type) {
49
-                Boolean::class -> serverState.setFeature(feature, true)
48
+                Boolean::class -> set(feature, true)
50 49
                 else -> TODO("not implemented")
51 50
             }
52 51
         }

+ 7
- 1
src/main/kotlin/com/dmdirc/ktirc/messages/MessageProcessor.kt View File

@@ -15,4 +15,10 @@ interface MessageProcessor {
15 15
      */
16 16
     fun process(message: IrcMessage): List<IrcEvent>
17 17
 
18
-}
18
+}
19
+
20
+val messageProcessors = setOf(
21
+        ISupportProcessor(),
22
+        PingProcessor(),
23
+        WelcomeProcessor()
24
+)

+ 3
- 10
src/main/kotlin/com/dmdirc/ktirc/messages/WelcomeProcessor.kt View File

@@ -1,19 +1,12 @@
1 1
 package com.dmdirc.ktirc.messages
2 2
 
3
-import com.dmdirc.ktirc.events.IrcEvent
4
-import com.dmdirc.ktirc.events.ServerConnected
3
+import com.dmdirc.ktirc.events.ServerWelcome
5 4
 import com.dmdirc.ktirc.io.IrcMessage
6
-import com.dmdirc.ktirc.state.ServerState
7 5
 
8
-class WelcomeProcessor(private val serverState: ServerState) : MessageProcessor {
6
+class WelcomeProcessor : MessageProcessor {
9 7
 
10 8
     override val commands = arrayOf("001")
11 9
 
12
-    override fun process(message: IrcMessage): List<IrcEvent> {
13
-        serverState.localNickname = String(message.params[0])
14
-
15
-        // TODO: Maybe this should be later, like after the first line received after 001-005
16
-        return listOf(ServerConnected)
17
-    }
10
+    override fun process(message: IrcMessage) = listOf(ServerWelcome(String(message.params[0])))
18 11
 
19 12
 }

src/main/kotlin/com/dmdirc/ktirc/state/ServerState.kt → src/main/kotlin/com/dmdirc/ktirc/model/ServerState.kt View File

@@ -1,37 +1,32 @@
1
-package com.dmdirc.ktirc.state
1
+package com.dmdirc.ktirc.model
2 2
 
3 3
 import com.dmdirc.ktirc.io.CaseMapping
4 4
 import kotlin.reflect.KClass
5 5
 
6
-interface ServerState {
6
+class ServerState(initialNickname: String) {
7 7
 
8
-    var localNickname: String
9
-
10
-    fun <T : Any> getFeature(feature: ServerFeature<T>): T?
11
-    fun setFeature(feature: ServerFeature<*>, value: Any)
12
-    fun resetFeature(feature: ServerFeature<*>): Any?
8
+    var localNickname: String = initialNickname
9
+    val features = ServerFeatureMap()
13 10
 
14 11
 }
15 12
 
16
-class IrcServerState(initialNickname: String) : ServerState {
17
-
18
-    override var localNickname: String = initialNickname
13
+class ServerFeatureMap {
19 14
 
20
-    private val features = HashMap<ServerFeature<*>, Any>()
15
+    private val features = HashMap<ServerFeature<*>, Any?>()
21 16
 
22 17
     @Suppress("UNCHECKED_CAST")
23
-    override fun <T : Any> getFeature(feature: ServerFeature<T>) = features.getOrDefault(feature, feature.default) as? T?
18
+    operator fun <T : Any> get(feature: ServerFeature<T>) = features.getOrDefault(feature, feature.default) as? T? ?: feature.default
24 19
 
25
-    override fun setFeature(feature: ServerFeature<*>, value: Any) {
20
+    operator fun set(feature: ServerFeature<*>, value: Any) {
26 21
         require(feature.type.isInstance(value))
27 22
         features[feature] = value
28 23
     }
29 24
 
30
-    override fun resetFeature(feature: ServerFeature<*>) = features.remove(feature)
25
+    fun setAll(featureMap: ServerFeatureMap) = featureMap.features.forEach { feature, value -> features[feature] = value }
26
+    fun reset(feature: ServerFeature<*>) = features.put(feature, null)
31 27
 
32 28
 }
33 29
 
34
-
35 30
 sealed class ServerFeature<T : Any>(val name: String, val type: KClass<T>, val default: T? = null) {
36 31
     object ServerCaseMapping : ServerFeature<CaseMapping>("CASEMAPPING", CaseMapping::class, CaseMapping.Rfc)
37 32
     object MaximumChannels : ServerFeature<Int>("CHANLIMIT", Int::class)

src/test/kotlin/com/dmdirc/ktirc/IrcClientTest.kt → src/test/kotlin/com/dmdirc/ktirc/IrcClientImplTest.kt View File

@@ -1,20 +1,15 @@
1 1
 package com.dmdirc.ktirc
2 2
 
3
-import com.dmdirc.ktirc.io.KtorLineBufferedSocket
4 3
 import com.dmdirc.ktirc.io.LineBufferedSocket
5 4
 import com.dmdirc.ktirc.model.Profile
6 5
 import com.dmdirc.ktirc.model.Server
7 6
 import com.nhaarman.mockitokotlin2.*
8
-import kotlinx.coroutines.channels.ArrayChannel
9 7
 import kotlinx.coroutines.channels.Channel
10
-import kotlinx.coroutines.channels.ReceiveChannel
11 8
 import kotlinx.coroutines.runBlocking
12
-import org.junit.jupiter.api.BeforeEach
13 9
 import org.junit.jupiter.api.Test
14 10
 import org.junit.jupiter.api.assertThrows
15
-import java.lang.IllegalStateException
16 11
 
17
-internal class IrcClientTest {
12
+internal class IrcClientImplTest {
18 13
 
19 14
     companion object {
20 15
         private const val HOST = "thegibson.com"
@@ -36,9 +31,9 @@ internal class IrcClientTest {
36 31
     }
37 32
 
38 33
     @Test
39
-    fun `IrcClient uses socket factory to create a new socket on connect`() {
34
+    fun `IrcClientImpl uses socket factory to create a new socket on connect`() {
40 35
         runBlocking {
41
-            val client = IrcClient(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
36
+            val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
42 37
             client.socketFactory = mockSocketFactory
43 38
             readLineChannel.close()
44 39
 
@@ -49,9 +44,9 @@ internal class IrcClientTest {
49 44
     }
50 45
 
51 46
     @Test
52
-    fun `IrcClient throws if socket already exists`() {
47
+    fun `IrcClientImpl throws if socket already exists`() {
53 48
         runBlocking {
54
-            val client = IrcClient(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
49
+            val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
55 50
             client.socketFactory = mockSocketFactory
56 51
             readLineChannel.close()
57 52
 
@@ -66,9 +61,9 @@ internal class IrcClientTest {
66 61
     }
67 62
 
68 63
     @Test
69
-    fun `IrcClient sends basic connection strings`() {
64
+    fun `IrcClientImpl sends basic connection strings`() {
70 65
         runBlocking {
71
-            val client = IrcClient(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
66
+            val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
72 67
             client.socketFactory = mockSocketFactory
73 68
             readLineChannel.close()
74 69
 
@@ -82,9 +77,9 @@ internal class IrcClientTest {
82 77
     }
83 78
 
84 79
     @Test
85
-    fun `IrcClient sends password first, when present`() {
80
+    fun `IrcClientImpl sends password first, when present`() {
86 81
         runBlocking {
87
-            val client = IrcClient(Server(HOST, PORT, password = PASSWORD), Profile(NICK, REAL_NAME, USER_NAME))
82
+            val client = IrcClientImpl(Server(HOST, PORT, password = PASSWORD), Profile(NICK, REAL_NAME, USER_NAME))
88 83
             client.socketFactory = mockSocketFactory
89 84
             readLineChannel.close()
90 85
 

+ 21
- 0
src/test/kotlin/com/dmdirc/ktirc/events/PingHandlerTest.kt View File

@@ -0,0 +1,21 @@
1
+package com.dmdirc.ktirc.events
2
+
3
+import com.dmdirc.ktirc.IrcClient
4
+import com.nhaarman.mockitokotlin2.mock
5
+import com.nhaarman.mockitokotlin2.verify
6
+import kotlinx.coroutines.runBlocking
7
+import org.junit.jupiter.api.Test
8
+
9
+internal class PingHandlerTest {
10
+
11
+    private val ircClient = mock<IrcClient>()
12
+
13
+    private val handler = PingHandler()
14
+
15
+    @Test
16
+    fun `PingHandler responses to pings with a pong`() = runBlocking {
17
+        handler.processEvent(ircClient, PingReceived("the_plague".toByteArray()))
18
+        verify(ircClient).send("PONG :the_plague")
19
+    }
20
+
21
+}

+ 40
- 0
src/test/kotlin/com/dmdirc/ktirc/events/ServerStateHandlerTest.kt View File

@@ -0,0 +1,40 @@
1
+package com.dmdirc.ktirc.events
2
+
3
+import com.dmdirc.ktirc.IrcClient
4
+import com.dmdirc.ktirc.model.ServerFeature
5
+import com.dmdirc.ktirc.model.ServerFeatureMap
6
+import com.dmdirc.ktirc.model.ServerState
7
+import com.nhaarman.mockitokotlin2.doReturn
8
+import com.nhaarman.mockitokotlin2.mock
9
+import kotlinx.coroutines.runBlocking
10
+import org.junit.jupiter.api.Assertions.assertEquals
11
+import org.junit.jupiter.api.Test
12
+
13
+internal class ServerStateHandlerTest {
14
+
15
+    private val serverState = ServerState("")
16
+    private val ircClient = mock<IrcClient> {
17
+        on { serverState } doReturn serverState
18
+    }
19
+
20
+    private val handler = ServerStateHandler()
21
+
22
+    @Test
23
+    fun `ServerStateHandler sets local nickname on welcome event`() = runBlocking {
24
+        handler.processEvent(ircClient, ServerWelcome("acidBurn"))
25
+        assertEquals("acidBurn", serverState.localNickname)
26
+    }
27
+
28
+    @Test
29
+    fun `ServerStateHandler updates features on features event`() = runBlocking {
30
+        val features = ServerFeatureMap()
31
+        features[ServerFeature.ChannelModes] = "abc"
32
+        features[ServerFeature.WhoxSupport] = true
33
+
34
+        handler.processEvent(ircClient, ServerFeaturesUpdated(features))
35
+
36
+        assertEquals("abc", serverState.features[ServerFeature.ChannelModes])
37
+        assertEquals(true, serverState.features[ServerFeature.WhoxSupport])
38
+    }
39
+
40
+}

+ 19
- 11
src/test/kotlin/com/dmdirc/ktirc/io/MessageHandlerTest.kt View File

@@ -1,7 +1,9 @@
1 1
 package com.dmdirc.ktirc.io
2 2
 
3
-import com.dmdirc.ktirc.events.IrcEvent
3
+import com.dmdirc.ktirc.IrcClient
4
+import com.dmdirc.ktirc.events.EventHandler
4 5
 import com.dmdirc.ktirc.events.ServerConnected
6
+import com.dmdirc.ktirc.events.ServerWelcome
5 7
 import com.dmdirc.ktirc.messages.MessageProcessor
6 8
 import com.nhaarman.mockitokotlin2.*
7 9
 import kotlinx.coroutines.channels.Channel
@@ -10,6 +12,8 @@ import org.junit.jupiter.api.Test
10 12
 
11 13
 internal class MessageHandlerTest {
12 14
 
15
+    private val ircClient = mock<IrcClient>()
16
+
13 17
     private val nickProcessor = mock<MessageProcessor> {
14 18
         on { commands } doReturn arrayOf("FOO", "NICK")
15 19
     }
@@ -20,13 +24,13 @@ internal class MessageHandlerTest {
20 24
 
21 25
     @Test
22 26
     fun `MessageHandler passes message on to correct processor`() = runBlocking {
23
-        val handler = MessageHandler(listOf(joinProcessor, nickProcessor)) {}
27
+        val handler = MessageHandler(listOf(joinProcessor, nickProcessor), emptyList())
24 28
         val message = IrcMessage(null, null, "JOIN", emptyList())
25 29
 
26 30
         with(Channel<IrcMessage>(1)) {
27 31
             send(message)
28 32
             close()
29
-            handler.processMessages(this)
33
+            handler.processMessages(ircClient, this)
30 34
         }
31 35
 
32 36
         verify(joinProcessor).process(message)
@@ -35,7 +39,7 @@ internal class MessageHandlerTest {
35 39
 
36 40
     @Test
37 41
     fun `MessageHandler reads multiple messages`() = runBlocking {
38
-        val handler = MessageHandler(listOf(joinProcessor, nickProcessor)) {}
42
+        val handler = MessageHandler(listOf(joinProcessor, nickProcessor), emptyList())
39 43
         val joinMessage = IrcMessage(null, null, "JOIN", emptyList())
40 44
         val nickMessage = IrcMessage(null, null, "NICK", emptyList())
41 45
         val otherMessage = IrcMessage(null, null, "OTHER", emptyList())
@@ -45,7 +49,7 @@ internal class MessageHandlerTest {
45 49
             send(nickMessage)
46 50
             send(otherMessage)
47 51
             close()
48
-            handler.processMessages(this)
52
+            handler.processMessages(ircClient, this)
49 53
         }
50 54
 
51 55
         with(inOrder(joinProcessor, nickProcessor)) {
@@ -56,19 +60,23 @@ internal class MessageHandlerTest {
56 60
     }
57 61
 
58 62
     @Test
59
-    fun `MessageHandler invokes event handler with returned events`() = runBlocking {
60
-        val eventHandler = mock<(IrcEvent) -> Unit>()
61
-        val handler = MessageHandler(listOf(joinProcessor, nickProcessor), eventHandler)
63
+    fun `MessageHandler invokes all event handler with all returned events`() = runBlocking {
64
+        val eventHandler1 = mock<EventHandler>()
65
+        val eventHandler2 = mock<EventHandler>()
66
+        val handler = MessageHandler(listOf(joinProcessor, nickProcessor), listOf(eventHandler1, eventHandler2))
62 67
         val joinMessage = IrcMessage(null, null, "JOIN", emptyList())
63
-        whenever(joinProcessor.process(any())).thenReturn(listOf(ServerConnected))
68
+        whenever(joinProcessor.process(any())).thenReturn(listOf(ServerConnected, ServerWelcome("abc")))
64 69
 
65 70
         with(Channel<IrcMessage>(1)) {
66 71
             send(joinMessage)
67 72
             close()
68
-            handler.processMessages(this)
73
+            handler.processMessages(ircClient, this)
69 74
         }
70 75
 
71
-        verify(eventHandler).invoke(ServerConnected)
76
+        verify(eventHandler1).processEvent(ircClient, ServerConnected)
77
+        verify(eventHandler1).processEvent(ircClient, ServerWelcome("abc"))
78
+        verify(eventHandler2).processEvent(ircClient, ServerConnected)
79
+        verify(eventHandler2).processEvent(ircClient, ServerWelcome("abc"))
72 80
     }
73 81
 
74 82
 }

+ 18
- 18
src/test/kotlin/com/dmdirc/ktirc/messages/ISupportProcessorTest.kt View File

@@ -2,17 +2,14 @@ package com.dmdirc.ktirc.messages
2 2
 
3 3
 import com.dmdirc.ktirc.io.CaseMapping
4 4
 import com.dmdirc.ktirc.io.IrcMessage
5
-import com.dmdirc.ktirc.state.ServerFeature
6
-import com.dmdirc.ktirc.state.ServerState
7
-import com.nhaarman.mockitokotlin2.mock
8
-import com.nhaarman.mockitokotlin2.verify
9
-import org.junit.jupiter.api.Assertions.assertTrue
5
+import com.dmdirc.ktirc.model.ServerFeature
6
+import com.dmdirc.ktirc.model.ServerFeatureMap
7
+import org.junit.jupiter.api.Assertions.*
10 8
 import org.junit.jupiter.api.Test
11 9
 
12 10
 internal class ISupportProcessorTest {
13 11
 
14
-    private val state = mock<ServerState>()
15
-    private val processor = ISupportProcessor(state)
12
+    private val processor = ISupportProcessor()
16 13
 
17 14
     @Test
18 15
     fun `ISupportProcessor can handle 005s`() {
@@ -21,43 +18,46 @@ internal class ISupportProcessorTest {
21 18
 
22 19
     @Test
23 20
     fun `ISupportProcessor handles multiple numeric arguments`() {
24
-        processor.process(IrcMessage(null, "server.com".toByteArray(), "005",
21
+        val events = processor.process(IrcMessage(null, "server.com".toByteArray(), "005",
25 22
                 listOf("nickname", "CHANLIMIT=123", "CHANNELLEN=456", "are supported blah blah").map { it.toByteArray() }))
26 23
 
27
-        verify(state).setFeature(ServerFeature.MaximumChannels, 123)
28
-        verify(state).setFeature(ServerFeature.MaximumChannelNameLength, 456)
24
+        assertEquals(123, events[0].serverFeatures[ServerFeature.MaximumChannels])
25
+        assertEquals(456, events[0].serverFeatures[ServerFeature.MaximumChannelNameLength])
29 26
     }
30 27
 
31 28
     @Test
32 29
     fun `ISupportProcessor handles string arguments`() {
33
-        processor.process(IrcMessage(null, "server.com".toByteArray(), "005",
30
+        val events = processor.process(IrcMessage(null, "server.com".toByteArray(), "005",
34 31
                 listOf("nickname", "CHANMODES=abcd", "are supported blah blah").map { it.toByteArray() }))
35 32
 
36
-        verify(state).setFeature(ServerFeature.ChannelModes, "abcd")
33
+        assertEquals("abcd", events[0].serverFeatures[ServerFeature.ChannelModes])
37 34
     }
38 35
 
39 36
     @Test
40 37
     fun `ISupportProcessor handles resetting arguments`() {
41
-        processor.process(IrcMessage(null, "server.com".toByteArray(), "005",
38
+        val events = processor.process(IrcMessage(null, "server.com".toByteArray(), "005",
42 39
                 listOf("nickname", "-CHANMODES", "are supported blah blah").map { it.toByteArray() }))
43 40
 
44
-        verify(state).resetFeature(ServerFeature.ChannelModes)
41
+        val oldFeatures = ServerFeatureMap()
42
+        oldFeatures[ServerFeature.ChannelModes] = "abc"
43
+        oldFeatures.setAll(events[0].serverFeatures)
44
+        assertNull(oldFeatures[ServerFeature.ChannelModes])
45 45
     }
46 46
 
47 47
     @Test
48 48
     fun `ISupportProcessor handles case mapping arguments`() {
49
-        processor.process(IrcMessage(null, "server.com".toByteArray(), "005",
49
+        val events = processor.process(IrcMessage(null, "server.com".toByteArray(), "005",
50 50
                 listOf("nickname", "CASEMAPPING=rfc1459-strict", "are supported blah blah").map { it.toByteArray() }))
51 51
 
52
-        verify(state).setFeature(ServerFeature.ServerCaseMapping, CaseMapping.RfcStrict)
52
+        assertEquals(CaseMapping.RfcStrict, events[0].serverFeatures[ServerFeature.ServerCaseMapping])
53 53
     }
54 54
 
55 55
     @Test
56 56
     fun `ISupportProcessor handles boolean features with no arguments`() {
57
-        processor.process(IrcMessage(null, "server.com".toByteArray(), "005",
57
+        val events = processor.process(IrcMessage(null, "server.com".toByteArray(), "005",
58 58
                 listOf("nickname", "WHOX", "are supported blah blah").map { it.toByteArray() }))
59 59
 
60
-        verify(state).setFeature(ServerFeature.WhoxSupport, true)
60
+        assertEquals(true, events[0].serverFeatures[ServerFeature.WhoxSupport])
61 61
     }
62 62
 
63 63
 }

+ 4
- 16
src/test/kotlin/com/dmdirc/ktirc/messages/WelcomeProcessorTest.kt View File

@@ -1,19 +1,15 @@
1 1
 package com.dmdirc.ktirc.messages
2 2
 
3 3
 import com.dmdirc.ktirc.events.IrcEvent
4
-import com.dmdirc.ktirc.events.ServerConnected
4
+import com.dmdirc.ktirc.events.ServerWelcome
5 5
 import com.dmdirc.ktirc.io.IrcMessage
6
-import com.dmdirc.ktirc.state.ServerState
7
-import com.nhaarman.mockitokotlin2.mock
8
-import com.nhaarman.mockitokotlin2.verify
9 6
 import org.junit.jupiter.api.Assertions.assertEquals
10 7
 import org.junit.jupiter.api.Assertions.assertTrue
11 8
 import org.junit.jupiter.api.Test
12 9
 
13 10
 internal class WelcomeProcessorTest {
14 11
 
15
-    private val state = mock<ServerState>()
16
-    private val processor = WelcomeProcessor(state)
12
+    private val processor = WelcomeProcessor()
17 13
 
18 14
     @Test
19 15
     fun `WelcomeProcessor can handle 001s`() {
@@ -21,19 +17,11 @@ internal class WelcomeProcessorTest {
21 17
     }
22 18
 
23 19
     @Test
24
-    fun `WelcomeProcessor parses local nickname`() {
25
-        processor.process(IrcMessage(null, ":thegibson.com".toByteArray(), "001", listOf(
26
-                "acidBurn".toByteArray(),
27
-                "Welcome to the Internet Relay Network, acidBurn!burn@hacktheplanet.com".toByteArray())))
28
-        verify(state).localNickname = "acidBurn"
29
-    }
30
-
31
-    @Test
32
-    fun `WelcomeProcessor returns server connected event`() {
20
+    fun `WelcomeProcessor returns server welcome event`() {
33 21
         val events = processor.process(IrcMessage(null, ":thegibson.com".toByteArray(), "001", listOf(
34 22
                 "acidBurn".toByteArray(),
35 23
                 "Welcome to the Internet Relay Network, acidBurn!burn@hacktheplanet.com".toByteArray())))
36
-        assertEquals(listOf<IrcEvent>(ServerConnected), events)
24
+        assertEquals(listOf<IrcEvent>(ServerWelcome("acidBurn")), events)
37 25
     }
38 26
 
39 27
 }

+ 68
- 0
src/test/kotlin/com/dmdirc/ktirc/model/ServerFeatureMapTest.kt View File

@@ -0,0 +1,68 @@
1
+package com.dmdirc.ktirc.model
2
+
3
+import com.dmdirc.ktirc.io.CaseMapping
4
+import org.junit.jupiter.api.Assertions.*
5
+import org.junit.jupiter.api.Test
6
+
7
+internal class ServerFeatureMapTest {
8
+
9
+    @Test
10
+    fun `ServerFeatureMap should return defaults for unspecified features`() {
11
+        val featureMap = ServerFeatureMap()
12
+        assertEquals(200, featureMap[ServerFeature.MaximumChannelNameLength])
13
+    }
14
+
15
+    @Test
16
+    fun `ServerFeatureMap should return null for unspecified features with no default`() {
17
+        val featureMap = ServerFeatureMap()
18
+        assertNull(featureMap[ServerFeature.ChannelModes])
19
+    }
20
+
21
+    @Test
22
+    fun `ServerFeatureMap should return previously set value for features`() {
23
+        val featureMap = ServerFeatureMap()
24
+        featureMap[ServerFeature.MaximumChannels] = 123
25
+        assertEquals(123, featureMap[ServerFeature.MaximumChannels])
26
+    }
27
+
28
+    @Test
29
+    fun `ServerFeatureMap should return default set value for features that were reset`() {
30
+        val featureMap = ServerFeatureMap()
31
+        featureMap[ServerFeature.MaximumChannels] = 123
32
+        featureMap.reset(ServerFeature.MaximumChannels)
33
+        assertNull(featureMap[ServerFeature.MaximumChannels])
34
+    }
35
+
36
+    @Test
37
+    fun `ServerFeatureMap should throw if a feature is set with the wrong type`() {
38
+        val featureMap = ServerFeatureMap()
39
+        assertThrows(IllegalArgumentException::class.java) {
40
+            featureMap[ServerFeature.MaximumChannels] = "123"
41
+        }
42
+    }
43
+
44
+    @Test
45
+    fun `ServerFeatureMap sets all features from another map`() {
46
+        val featureMap1 = ServerFeatureMap()
47
+        val featureMap2 = ServerFeatureMap()
48
+        featureMap2[ServerFeature.WhoxSupport] = true
49
+        featureMap2[ServerFeature.ChannelModes] = "abc"
50
+        featureMap1.setAll(featureMap2)
51
+
52
+        assertEquals(true, featureMap1[ServerFeature.WhoxSupport])
53
+        assertEquals("abc", featureMap1[ServerFeature.ChannelModes])
54
+    }
55
+
56
+
57
+    @Test
58
+    fun `ServerFeatureMap resets features reset in another map`() {
59
+        val featureMap1 = ServerFeatureMap()
60
+        val featureMap2 = ServerFeatureMap()
61
+        featureMap1[ServerFeature.ServerCaseMapping] = CaseMapping.RfcStrict
62
+        featureMap2.reset(ServerFeature.ServerCaseMapping)
63
+        featureMap1.setAll(featureMap2)
64
+
65
+        assertEquals(CaseMapping.Rfc, featureMap1[ServerFeature.ServerCaseMapping])
66
+    }
67
+    
68
+}

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

@@ -0,0 +1,14 @@
1
+package com.dmdirc.ktirc.model
2
+
3
+import org.junit.jupiter.api.Assertions.assertEquals
4
+import org.junit.jupiter.api.Test
5
+
6
+internal class ServerStateTest {
7
+
8
+    @Test
9
+    fun `IrcServerState should use the initial nickname as local nickname`() {
10
+        val serverState = ServerState("acidBurn")
11
+        assertEquals("acidBurn", serverState.localNickname)
12
+    }
13
+
14
+}

+ 0
- 50
src/test/kotlin/com/dmdirc/ktirc/state/IrcServerStateTest.kt View File

@@ -1,50 +0,0 @@
1
-package com.dmdirc.ktirc.state
2
-
3
-import org.junit.jupiter.api.Assertions.*
4
-import org.junit.jupiter.api.Test
5
-import java.lang.IllegalArgumentException
6
-
7
-internal class IrcServerStateTest {
8
-
9
-    @Test
10
-    fun `IrcServerState should return defaults for unspecified features`() {
11
-        val serverState = IrcServerState("")
12
-        assertEquals(200, serverState.getFeature(ServerFeature.MaximumChannelNameLength))
13
-    }
14
-
15
-    @Test
16
-    fun `IrcServerState should return null for unspecified features with no default`() {
17
-        val serverState = IrcServerState("")
18
-        assertNull(serverState.getFeature(ServerFeature.ChannelModes))
19
-    }
20
-
21
-    @Test
22
-    fun `IrcServerState should return previously set value for features`() {
23
-        val serverState = IrcServerState("")
24
-        serverState.setFeature(ServerFeature.MaximumChannels, 123)
25
-        assertEquals(123, serverState.getFeature(ServerFeature.MaximumChannels))
26
-    }
27
-
28
-    @Test
29
-    fun `IrcServerState should return default set value for features that were reset`() {
30
-        val serverState = IrcServerState("")
31
-        serverState.setFeature(ServerFeature.MaximumChannels, 123)
32
-        serverState.resetFeature(ServerFeature.MaximumChannels)
33
-        assertNull(serverState.getFeature(ServerFeature.MaximumChannels))
34
-    }
35
-
36
-    @Test
37
-    fun `IrcServerState should throw if a feature is set with the wrong type`() {
38
-        val serverState = IrcServerState("")
39
-        assertThrows(IllegalArgumentException::class.java) {
40
-            serverState.setFeature(ServerFeature.MaximumChannels, "123")
41
-        }
42
-    }
43
-
44
-    @Test
45
-    fun `IrcServerState should use the initial nickname as local nickname`() {
46
-        val serverState = IrcServerState("acidBurn")
47
-        assertEquals("acidBurn", serverState.localNickname)
48
-    }
49
-
50
-}

Loading…
Cancel
Save