Parcourir la source

Introduce event handlers, refactor message processors.

tags/v0.1.0
Chris Smith il y a 5 ans
Parent
révision
f2e081e6c7

+ 88
- 0
src/main/kotlin/com/dmdirc/ktirc/IrcClient.kt Voir le fichier

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 Voir le fichier

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 Voir le fichier

2
 
2
 
3
 package com.dmdirc.ktirc.events
3
 package com.dmdirc.ktirc.events
4
 
4
 
5
+import com.dmdirc.ktirc.model.ServerFeatureMap
6
+
5
 sealed class IrcEvent
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
 object ServerConnected : IrcEvent()
23
 object ServerConnected : IrcEvent()
24
+
25
+/**
26
+ * Raised whenever a PING is received from the server.
27
+ */
7
 data class PingReceived(val nonce: ByteArray): IrcEvent()
28
 data class PingReceived(val nonce: ByteArray): IrcEvent()

+ 14
- 0
src/main/kotlin/com/dmdirc/ktirc/events/PingHandler.kt Voir le fichier

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 Voir le fichier

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 Voir le fichier

1
 package com.dmdirc.ktirc.io
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
 import com.dmdirc.ktirc.messages.MessageProcessor
5
 import com.dmdirc.ktirc.messages.MessageProcessor
5
 import com.dmdirc.ktirc.util.logger
6
 import com.dmdirc.ktirc.util.logger
6
 import kotlinx.coroutines.channels.ReceiveChannel
7
 import kotlinx.coroutines.channels.ReceiveChannel
7
 import kotlinx.coroutines.channels.consumeEach
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
     private val log by logger()
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
     private fun IrcMessage.process() = this.getProcessor()?.process(this) ?: emptyList()
24
     private fun IrcMessage.process() = this.getProcessor()?.process(this) ?: emptyList()

+ 13
- 14
src/main/kotlin/com/dmdirc/ktirc/messages/ISupportProcessor.kt Voir le fichier

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

+ 7
- 1
src/main/kotlin/com/dmdirc/ktirc/messages/MessageProcessor.kt Voir le fichier

15
      */
15
      */
16
     fun process(message: IrcMessage): List<IrcEvent>
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 Voir le fichier

1
 package com.dmdirc.ktirc.messages
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
 import com.dmdirc.ktirc.io.IrcMessage
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
     override val commands = arrayOf("001")
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 Voir le fichier

1
-package com.dmdirc.ktirc.state
1
+package com.dmdirc.ktirc.model
2
 
2
 
3
 import com.dmdirc.ktirc.io.CaseMapping
3
 import com.dmdirc.ktirc.io.CaseMapping
4
 import kotlin.reflect.KClass
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
     @Suppress("UNCHECKED_CAST")
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
         require(feature.type.isInstance(value))
21
         require(feature.type.isInstance(value))
27
         features[feature] = value
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
 sealed class ServerFeature<T : Any>(val name: String, val type: KClass<T>, val default: T? = null) {
30
 sealed class ServerFeature<T : Any>(val name: String, val type: KClass<T>, val default: T? = null) {
36
     object ServerCaseMapping : ServerFeature<CaseMapping>("CASEMAPPING", CaseMapping::class, CaseMapping.Rfc)
31
     object ServerCaseMapping : ServerFeature<CaseMapping>("CASEMAPPING", CaseMapping::class, CaseMapping.Rfc)
37
     object MaximumChannels : ServerFeature<Int>("CHANLIMIT", Int::class)
32
     object MaximumChannels : ServerFeature<Int>("CHANLIMIT", Int::class)

src/test/kotlin/com/dmdirc/ktirc/IrcClientTest.kt → src/test/kotlin/com/dmdirc/ktirc/IrcClientImplTest.kt Voir le fichier

1
 package com.dmdirc.ktirc
1
 package com.dmdirc.ktirc
2
 
2
 
3
-import com.dmdirc.ktirc.io.KtorLineBufferedSocket
4
 import com.dmdirc.ktirc.io.LineBufferedSocket
3
 import com.dmdirc.ktirc.io.LineBufferedSocket
5
 import com.dmdirc.ktirc.model.Profile
4
 import com.dmdirc.ktirc.model.Profile
6
 import com.dmdirc.ktirc.model.Server
5
 import com.dmdirc.ktirc.model.Server
7
 import com.nhaarman.mockitokotlin2.*
6
 import com.nhaarman.mockitokotlin2.*
8
-import kotlinx.coroutines.channels.ArrayChannel
9
 import kotlinx.coroutines.channels.Channel
7
 import kotlinx.coroutines.channels.Channel
10
-import kotlinx.coroutines.channels.ReceiveChannel
11
 import kotlinx.coroutines.runBlocking
8
 import kotlinx.coroutines.runBlocking
12
-import org.junit.jupiter.api.BeforeEach
13
 import org.junit.jupiter.api.Test
9
 import org.junit.jupiter.api.Test
14
 import org.junit.jupiter.api.assertThrows
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
     companion object {
14
     companion object {
20
         private const val HOST = "thegibson.com"
15
         private const val HOST = "thegibson.com"
36
     }
31
     }
37
 
32
 
38
     @Test
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
         runBlocking {
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
             client.socketFactory = mockSocketFactory
37
             client.socketFactory = mockSocketFactory
43
             readLineChannel.close()
38
             readLineChannel.close()
44
 
39
 
49
     }
44
     }
50
 
45
 
51
     @Test
46
     @Test
52
-    fun `IrcClient throws if socket already exists`() {
47
+    fun `IrcClientImpl throws if socket already exists`() {
53
         runBlocking {
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
             client.socketFactory = mockSocketFactory
50
             client.socketFactory = mockSocketFactory
56
             readLineChannel.close()
51
             readLineChannel.close()
57
 
52
 
66
     }
61
     }
67
 
62
 
68
     @Test
63
     @Test
69
-    fun `IrcClient sends basic connection strings`() {
64
+    fun `IrcClientImpl sends basic connection strings`() {
70
         runBlocking {
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
             client.socketFactory = mockSocketFactory
67
             client.socketFactory = mockSocketFactory
73
             readLineChannel.close()
68
             readLineChannel.close()
74
 
69
 
82
     }
77
     }
83
 
78
 
84
     @Test
79
     @Test
85
-    fun `IrcClient sends password first, when present`() {
80
+    fun `IrcClientImpl sends password first, when present`() {
86
         runBlocking {
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
             client.socketFactory = mockSocketFactory
83
             client.socketFactory = mockSocketFactory
89
             readLineChannel.close()
84
             readLineChannel.close()
90
 
85
 

+ 21
- 0
src/test/kotlin/com/dmdirc/ktirc/events/PingHandlerTest.kt Voir le fichier

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 Voir le fichier

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 Voir le fichier

1
 package com.dmdirc.ktirc.io
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
 import com.dmdirc.ktirc.events.ServerConnected
5
 import com.dmdirc.ktirc.events.ServerConnected
6
+import com.dmdirc.ktirc.events.ServerWelcome
5
 import com.dmdirc.ktirc.messages.MessageProcessor
7
 import com.dmdirc.ktirc.messages.MessageProcessor
6
 import com.nhaarman.mockitokotlin2.*
8
 import com.nhaarman.mockitokotlin2.*
7
 import kotlinx.coroutines.channels.Channel
9
 import kotlinx.coroutines.channels.Channel
10
 
12
 
11
 internal class MessageHandlerTest {
13
 internal class MessageHandlerTest {
12
 
14
 
15
+    private val ircClient = mock<IrcClient>()
16
+
13
     private val nickProcessor = mock<MessageProcessor> {
17
     private val nickProcessor = mock<MessageProcessor> {
14
         on { commands } doReturn arrayOf("FOO", "NICK")
18
         on { commands } doReturn arrayOf("FOO", "NICK")
15
     }
19
     }
20
 
24
 
21
     @Test
25
     @Test
22
     fun `MessageHandler passes message on to correct processor`() = runBlocking {
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
         val message = IrcMessage(null, null, "JOIN", emptyList())
28
         val message = IrcMessage(null, null, "JOIN", emptyList())
25
 
29
 
26
         with(Channel<IrcMessage>(1)) {
30
         with(Channel<IrcMessage>(1)) {
27
             send(message)
31
             send(message)
28
             close()
32
             close()
29
-            handler.processMessages(this)
33
+            handler.processMessages(ircClient, this)
30
         }
34
         }
31
 
35
 
32
         verify(joinProcessor).process(message)
36
         verify(joinProcessor).process(message)
35
 
39
 
36
     @Test
40
     @Test
37
     fun `MessageHandler reads multiple messages`() = runBlocking {
41
     fun `MessageHandler reads multiple messages`() = runBlocking {
38
-        val handler = MessageHandler(listOf(joinProcessor, nickProcessor)) {}
42
+        val handler = MessageHandler(listOf(joinProcessor, nickProcessor), emptyList())
39
         val joinMessage = IrcMessage(null, null, "JOIN", emptyList())
43
         val joinMessage = IrcMessage(null, null, "JOIN", emptyList())
40
         val nickMessage = IrcMessage(null, null, "NICK", emptyList())
44
         val nickMessage = IrcMessage(null, null, "NICK", emptyList())
41
         val otherMessage = IrcMessage(null, null, "OTHER", emptyList())
45
         val otherMessage = IrcMessage(null, null, "OTHER", emptyList())
45
             send(nickMessage)
49
             send(nickMessage)
46
             send(otherMessage)
50
             send(otherMessage)
47
             close()
51
             close()
48
-            handler.processMessages(this)
52
+            handler.processMessages(ircClient, this)
49
         }
53
         }
50
 
54
 
51
         with(inOrder(joinProcessor, nickProcessor)) {
55
         with(inOrder(joinProcessor, nickProcessor)) {
56
     }
60
     }
57
 
61
 
58
     @Test
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
         val joinMessage = IrcMessage(null, null, "JOIN", emptyList())
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
         with(Channel<IrcMessage>(1)) {
70
         with(Channel<IrcMessage>(1)) {
66
             send(joinMessage)
71
             send(joinMessage)
67
             close()
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 Voir le fichier

2
 
2
 
3
 import com.dmdirc.ktirc.io.CaseMapping
3
 import com.dmdirc.ktirc.io.CaseMapping
4
 import com.dmdirc.ktirc.io.IrcMessage
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
 import org.junit.jupiter.api.Test
8
 import org.junit.jupiter.api.Test
11
 
9
 
12
 internal class ISupportProcessorTest {
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
     @Test
14
     @Test
18
     fun `ISupportProcessor can handle 005s`() {
15
     fun `ISupportProcessor can handle 005s`() {
21
 
18
 
22
     @Test
19
     @Test
23
     fun `ISupportProcessor handles multiple numeric arguments`() {
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
                 listOf("nickname", "CHANLIMIT=123", "CHANNELLEN=456", "are supported blah blah").map { it.toByteArray() }))
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
     @Test
28
     @Test
32
     fun `ISupportProcessor handles string arguments`() {
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
                 listOf("nickname", "CHANMODES=abcd", "are supported blah blah").map { it.toByteArray() }))
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
     @Test
36
     @Test
40
     fun `ISupportProcessor handles resetting arguments`() {
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
                 listOf("nickname", "-CHANMODES", "are supported blah blah").map { it.toByteArray() }))
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
     @Test
47
     @Test
48
     fun `ISupportProcessor handles case mapping arguments`() {
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
                 listOf("nickname", "CASEMAPPING=rfc1459-strict", "are supported blah blah").map { it.toByteArray() }))
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
     @Test
55
     @Test
56
     fun `ISupportProcessor handles boolean features with no arguments`() {
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
                 listOf("nickname", "WHOX", "are supported blah blah").map { it.toByteArray() }))
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 Voir le fichier

1
 package com.dmdirc.ktirc.messages
1
 package com.dmdirc.ktirc.messages
2
 
2
 
3
 import com.dmdirc.ktirc.events.IrcEvent
3
 import com.dmdirc.ktirc.events.IrcEvent
4
-import com.dmdirc.ktirc.events.ServerConnected
4
+import com.dmdirc.ktirc.events.ServerWelcome
5
 import com.dmdirc.ktirc.io.IrcMessage
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
 import org.junit.jupiter.api.Assertions.assertEquals
6
 import org.junit.jupiter.api.Assertions.assertEquals
10
 import org.junit.jupiter.api.Assertions.assertTrue
7
 import org.junit.jupiter.api.Assertions.assertTrue
11
 import org.junit.jupiter.api.Test
8
 import org.junit.jupiter.api.Test
12
 
9
 
13
 internal class WelcomeProcessorTest {
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
     @Test
14
     @Test
19
     fun `WelcomeProcessor can handle 001s`() {
15
     fun `WelcomeProcessor can handle 001s`() {
21
     }
17
     }
22
 
18
 
23
     @Test
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
         val events = processor.process(IrcMessage(null, ":thegibson.com".toByteArray(), "001", listOf(
21
         val events = processor.process(IrcMessage(null, ":thegibson.com".toByteArray(), "001", listOf(
34
                 "acidBurn".toByteArray(),
22
                 "acidBurn".toByteArray(),
35
                 "Welcome to the Internet Relay Network, acidBurn!burn@hacktheplanet.com".toByteArray())))
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 Voir le fichier

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 Voir le fichier

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 Voir le fichier

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
-}

Chargement…
Annuler
Enregistrer