Browse Source

Add BATCH support

Closes #11
tags/v0.9.0
Chris Smith 5 years ago
parent
commit
92c642e550

+ 3
- 0
CHANGELOG View File

@@ -7,6 +7,9 @@ vNEXT (in development)
7 7
  * Events now have a `metadata` property instead of a `time` (and time is available in metadata)
8 8
    * IrcEvent.time is now deprecated but will remain until after v1.0.0.
9 9
    * Metadata now contains the event's batch ID, if any.
10
+ * Added support for batches
11
+   * All events in a batch are buffered until the batch is finished
12
+   * The events are then published together in a single `BatchReceived` event
10 13
  * (Internal) Introduced event mutators
11 14
    * Event mutators are now responsible for handling changing events in response to state
12 15
      e.g. ChannelFanOutMutator creates Channel* events for global quits/nick changes/etc

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

@@ -1,7 +1,6 @@
1 1
 package com.dmdirc.ktirc
2 2
 
3 3
 import com.dmdirc.ktirc.events.*
4
-import com.dmdirc.ktirc.events.handlers.EventHandler
5 4
 import com.dmdirc.ktirc.events.handlers.eventHandlers
6 5
 import com.dmdirc.ktirc.events.mutators.eventMutators
7 6
 import com.dmdirc.ktirc.io.KtorLineBufferedSocket
@@ -42,7 +41,7 @@ internal class IrcClientImpl(private val config: IrcClientConfig) : IrcClient, C
42 41
     override val channelState = ChannelStateMap { caseMapping }
43 42
     override val userState = UserState { caseMapping }
44 43
 
45
-    private val messageHandler = MessageHandler(messageProcessors, eventMutators, eventHandlers.toMutableList())
44
+    private val messageHandler = MessageHandler(messageProcessors, eventMutators, eventHandlers)
46 45
 
47 46
     private val parser = MessageParser()
48 47
     private var socket: LineBufferedSocket? = null
@@ -85,15 +84,9 @@ internal class IrcClientImpl(private val config: IrcClientConfig) : IrcClient, C
85 84
         socket?.disconnect()
86 85
     }
87 86
 
88
-    override fun onEvent(handler: (IrcEvent) -> Unit) {
89
-        messageHandler.handlers.add(object : EventHandler {
90
-            override fun processEvent(client: IrcClient, event: IrcEvent) {
91
-                handler(event)
92
-            }
93
-        })
94
-    }
87
+    override fun onEvent(handler: (IrcEvent) -> Unit) = messageHandler.addEmitter(handler)
95 88
 
96
-    private fun emitEvent(event: IrcEvent) = messageHandler.emitEvent(this, event)
89
+    private fun emitEvent(event: IrcEvent) = messageHandler.handleEvent(this, event)
97 90
     private fun sendPasswordIfPresent() = config.server.password?.let(this::sendPassword)
98 91
 
99 92
     internal fun reset() {

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

@@ -152,3 +152,6 @@ class BatchStarted(metadata: EventMetadata, val referenceId: String, val batchTy
152 152
 
153 153
 /** Indicates a batch of messages has finished. */
154 154
 class BatchFinished(metadata: EventMetadata, val referenceId: String) : IrcEvent(metadata)
155
+
156
+/** A batch of events that should be handled together. */
157
+class BatchReceived(metadata: EventMetadata, val type: String, val params: Array<String>, val events: List<IrcEvent>) : IrcEvent(metadata)

+ 49
- 0
src/main/kotlin/com/dmdirc/ktirc/events/mutators/BatchMutator.kt View File

@@ -0,0 +1,49 @@
1
+package com.dmdirc.ktirc.events.mutators
2
+
3
+import com.dmdirc.ktirc.IrcClient
4
+import com.dmdirc.ktirc.events.BatchFinished
5
+import com.dmdirc.ktirc.events.BatchReceived
6
+import com.dmdirc.ktirc.events.BatchStarted
7
+import com.dmdirc.ktirc.events.IrcEvent
8
+import com.dmdirc.ktirc.io.MessageEmitter
9
+import com.dmdirc.ktirc.model.Batch
10
+
11
+internal class BatchMutator : EventMutator {
12
+
13
+    override fun mutateEvent(client: IrcClient, messageEmitter: MessageEmitter, event: IrcEvent): List<IrcEvent> {
14
+        when {
15
+            event is BatchStarted -> startBatch(client, event)
16
+            event is BatchFinished -> return finishBatch(client, event)
17
+            event.metadata.batchId != null -> addToBatch(client, messageEmitter, event)
18
+            else -> return listOf(event)
19
+        }
20
+        return emptyList()
21
+    }
22
+
23
+    private fun startBatch(client: IrcClient, event: BatchStarted) {
24
+        client.serverState.batches[event.referenceId] =
25
+                Batch(event.batchType, event.params.asList(), event.metadata.batchId, mutableListOf(event))
26
+    }
27
+
28
+    private fun finishBatch(client: IrcClient, event: BatchFinished): List<IrcEvent> {
29
+        client.serverState.batches.remove(event.referenceId)?.let {
30
+            it.events += event
31
+            val batch = BatchReceived(it.events[0].metadata, it.type, it.arguments.toTypedArray(), it.events)
32
+            if (it.parent == null) {
33
+                return listOf(batch)
34
+            } else {
35
+                client.serverState.batches[it.parent]?.events?.add(batch)
36
+            }
37
+        }
38
+
39
+        return emptyList()
40
+    }
41
+
42
+    private fun addToBatch(client: IrcClient, messageEmitter: MessageEmitter, event: IrcEvent) {
43
+        client.serverState.batches[event.metadata.batchId]?.let {
44
+            it.events += event
45
+            messageEmitter.handleEvent(client, event, true)
46
+        }
47
+    }
48
+
49
+}

+ 2
- 1
src/main/kotlin/com/dmdirc/ktirc/events/mutators/ChannelFanOutMutator.kt View File

@@ -2,13 +2,14 @@ package com.dmdirc.ktirc.events.mutators
2 2
 
3 3
 import com.dmdirc.ktirc.IrcClient
4 4
 import com.dmdirc.ktirc.events.*
5
+import com.dmdirc.ktirc.io.MessageEmitter
5 6
 
6 7
 /**
7 8
  * "Fans out" global events such as quits and nick changes to each channel a user is in.
8 9
  */
9 10
 internal class ChannelFanOutMutator : EventMutator {
10 11
 
11
-    override fun mutateEvent(client: IrcClient, event: IrcEvent) = sequence<IrcEvent> {
12
+    override fun mutateEvent(client: IrcClient, messageEmitter: MessageEmitter, event: IrcEvent) = sequence<IrcEvent> {
12 13
         yield(event)
13 14
         when (event) {
14 15
             is UserQuit -> handleQuit(client, event)

+ 4
- 2
src/main/kotlin/com/dmdirc/ktirc/events/mutators/EventMutator.kt View File

@@ -2,15 +2,17 @@ package com.dmdirc.ktirc.events.mutators
2 2
 
3 3
 import com.dmdirc.ktirc.IrcClient
4 4
 import com.dmdirc.ktirc.events.IrcEvent
5
+import com.dmdirc.ktirc.io.MessageEmitter
5 6
 
6 7
 @FunctionalInterface
7 8
 internal interface EventMutator {
8 9
 
9
-    fun mutateEvent(client: IrcClient, event: IrcEvent): List<IrcEvent>
10
+    fun mutateEvent(client: IrcClient, messageEmitter: MessageEmitter, event: IrcEvent): List<IrcEvent>
10 11
 
11 12
 }
12 13
 
13 14
 internal val eventMutators = listOf(
14 15
         ServerReadyMutator(),
15
-        ChannelFanOutMutator()
16
+        ChannelFanOutMutator(),
17
+        BatchMutator()
16 18
 )

+ 2
- 1
src/main/kotlin/com/dmdirc/ktirc/events/mutators/ServerReadyMutator.kt View File

@@ -2,6 +2,7 @@ package com.dmdirc.ktirc.events.mutators
2 2
 
3 3
 import com.dmdirc.ktirc.IrcClient
4 4
 import com.dmdirc.ktirc.events.*
5
+import com.dmdirc.ktirc.io.MessageEmitter
5 6
 import com.dmdirc.ktirc.model.ServerStatus
6 7
 
7 8
 /**
@@ -24,7 +25,7 @@ internal class ServerReadyMutator : EventMutator {
24 25
             ServerCapabilitiesFinished::class
25 26
     )
26 27
 
27
-    override fun mutateEvent(client: IrcClient, event: IrcEvent): List<IrcEvent> = sequence {
28
+    override fun mutateEvent(client: IrcClient, messageEmitter: MessageEmitter, event: IrcEvent): List<IrcEvent> = sequence {
28 29
         if (client.serverState.receivedWelcome
29 30
                 && client.serverState.status == ServerStatus.Negotiating
30 31
                 && event::class !in excludedEvents) {

+ 30
- 10
src/main/kotlin/com/dmdirc/ktirc/io/MessageHandler.kt View File

@@ -9,30 +9,50 @@ import com.dmdirc.ktirc.model.IrcMessage
9 9
 import com.dmdirc.ktirc.util.logger
10 10
 import kotlinx.coroutines.channels.ReceiveChannel
11 11
 
12
+internal interface MessageEmitter {
13
+
14
+    fun handleEvent(ircClient: IrcClient, ircEvent: IrcEvent, processOnly: Boolean = false) = handleEvents(ircClient, listOf(ircEvent), processOnly)
15
+
16
+    fun handleEvents(ircClient: IrcClient, ircEvents: List<IrcEvent>, processOnly: Boolean = false)
17
+
18
+}
19
+
12 20
 internal class MessageHandler(
13 21
         private val processors: List<MessageProcessor>,
14 22
         private val mutators: List<EventMutator>,
15
-        val handlers: MutableList<EventHandler>) {
23
+        private val handlers: List<EventHandler>) : MessageEmitter {
16 24
 
17 25
     private val log by logger()
18 26
 
27
+    private val emitters = mutableListOf<(IrcEvent) -> Unit>()
28
+
19 29
     suspend fun processMessages(ircClient: IrcClient, messages: ReceiveChannel<IrcMessage>) {
20 30
         for (message in messages) {
21
-            emitEvents(ircClient, message.toEvents())
31
+            handleEvents(ircClient, message.toEvents())
22 32
         }
23 33
     }
24 34
 
25
-    fun emitEvent(ircClient: IrcClient, ircEvent: IrcEvent) = emitEvents(ircClient, listOf(ircEvent))
26
-
27
-    fun emitEvents(ircClient: IrcClient, ircEvents: List<IrcEvent>) {
28
-        mutators.fold(ircEvents) { events, mutator ->
29
-            events.flatMap { mutator.mutateEvent(ircClient, it) }
30
-        }.forEach { event ->
31
-            log.fine { "Dispatching event of type ${event::class}" }
32
-            handlers.forEach { it.processEvent(ircClient, event) }
35
+    override fun handleEvents(ircClient: IrcClient, ircEvents: List<IrcEvent>, processOnly: Boolean) {
36
+        val events = if (processOnly) ircEvents else ircEvents.mutate(ircClient)
37
+        events.forEach { event ->
38
+            event.process(ircClient)
39
+            if (!processOnly) {
40
+                log.fine { "Dispatching event of type ${event::class}" }
41
+                emitters.forEach { it(event) }
42
+            }
33 43
         }
34 44
     }
35 45
 
46
+    fun addEmitter(emitter: (IrcEvent) -> Unit) {
47
+        emitters.add(emitter)
48
+    }
49
+
50
+    private fun IrcEvent.process(ircClient: IrcClient) = handlers.forEach { it.processEvent(ircClient, this) }
51
+
52
+    private fun List<IrcEvent>.mutate(ircClient: IrcClient) = mutators.fold(this) { events, mutator ->
53
+        events.flatMap { mutator.mutateEvent(ircClient, this@MessageHandler, it) }
54
+    }
55
+
36 56
     private fun IrcMessage.toEvents() = this.getProcessor()?.process(this) ?: emptyList()
37 57
     private fun IrcMessage.getProcessor() = processors.firstOrNull { it.commands.contains(command) } ?: run {
38 58
         log.warning { "No processor found for $command" }

+ 9
- 1
src/main/kotlin/com/dmdirc/ktirc/messages/BatchProcessor.kt View File

@@ -1,5 +1,7 @@
1 1
 package com.dmdirc.ktirc.messages
2 2
 
3
+import com.dmdirc.ktirc.events.BatchFinished
4
+import com.dmdirc.ktirc.events.BatchStarted
3 5
 import com.dmdirc.ktirc.events.IrcEvent
4 6
 import com.dmdirc.ktirc.model.IrcMessage
5 7
 
@@ -8,7 +10,13 @@ internal class BatchProcessor : MessageProcessor {
8 10
     override val commands = arrayOf("BATCH")
9 11
 
10 12
     override fun process(message: IrcMessage): List<IrcEvent> {
11
-        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
13
+        val args = message.params.map { String(it) }
14
+        val id = args[0]
15
+        return when (id[0]) {
16
+            '+' -> listOf(BatchStarted(message.metadata, id.substring(1), args[1], args.subList(2, args.size).toTypedArray()))
17
+            '-' -> listOf(BatchFinished(message.metadata, id.substring(1)))
18
+            else -> emptyList()
19
+        }
12 20
     }
13 21
 
14 22
 }

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

@@ -20,6 +20,7 @@ internal interface MessageProcessor {
20 20
 internal val messageProcessors = listOf(
21 21
         AccountProcessor(),
22 22
         AuthenticationProcessor(),
23
+        BatchProcessor(),
23 24
         CapabilityProcessor(),
24 25
         ISupportProcessor(),
25 26
         JoinProcessor(),

+ 12
- 0
src/main/kotlin/com/dmdirc/ktirc/model/ServerState.kt View File

@@ -1,6 +1,7 @@
1 1
 package com.dmdirc.ktirc.model
2 2
 
3 3
 import com.dmdirc.ktirc.SaslConfig
4
+import com.dmdirc.ktirc.events.IrcEvent
4 5
 import com.dmdirc.ktirc.io.CaseMapping
5 6
 import com.dmdirc.ktirc.util.logger
6 7
 import kotlin.reflect.KClass
@@ -63,6 +64,11 @@ class ServerState internal constructor(
63 64
     val channelTypes
64 65
         get() = features[ServerFeature.ChannelTypes] ?: throw IllegalStateException("lost channel types")
65 66
 
67
+    /**
68
+     * Batches that are currently in progress.
69
+     */
70
+    internal val batches = mutableMapOf<String, Batch>()
71
+
66 72
     /**
67 73
      * Determines if the given mode is one applied to a user of a channel, such as 'o' for operator.
68 74
      */
@@ -92,6 +98,7 @@ class ServerState internal constructor(
92 98
         features.clear()
93 99
         capabilities.reset()
94 100
         sasl.reset()
101
+        batches.clear()
95 102
     }
96 103
 
97 104
 }
@@ -176,6 +183,11 @@ enum class ServerStatus {
176 183
     Ready,
177 184
 }
178 185
 
186
+/**
187
+ * Represents an in-progress batch.
188
+ */
189
+internal data class Batch(val type: String, val arguments: List<String>, val parent: String? = null, val events: MutableList<IrcEvent> = mutableListOf())
190
+
179 191
 internal val serverFeatures: Map<String, ServerFeature<*>> by lazy {
180 192
     ServerFeature::class.nestedClasses.map { it.objectInstance as ServerFeature<*> }.associateBy { it.name }
181 193
 }

+ 121
- 0
src/test/kotlin/com/dmdirc/ktirc/events/mutators/BatchMutatorTest.kt View File

@@ -0,0 +1,121 @@
1
+package com.dmdirc.ktirc.events.mutators
2
+
3
+import com.dmdirc.ktirc.IrcClient
4
+import com.dmdirc.ktirc.TestConstants
5
+import com.dmdirc.ktirc.events.*
6
+import com.dmdirc.ktirc.io.MessageEmitter
7
+import com.dmdirc.ktirc.model.Batch
8
+import com.dmdirc.ktirc.model.ServerState
9
+import com.dmdirc.ktirc.model.User
10
+import com.nhaarman.mockitokotlin2.*
11
+import org.junit.jupiter.api.Assertions.*
12
+import org.junit.jupiter.api.Test
13
+
14
+internal class BatchMutatorTest {
15
+
16
+    private val mutator = BatchMutator()
17
+
18
+    private val serverState = ServerState("", "")
19
+    private val messageEmitter = mock<MessageEmitter>()
20
+    private val ircClient = mock<IrcClient> {
21
+        on { serverState } doReturn serverState
22
+    }
23
+
24
+    @Test
25
+    fun `returns non-batched events unmodified`() {
26
+        val event = ServerWelcome(EventMetadata(TestConstants.time), "the.gibson", "acidBurn")
27
+        val events = mutator.mutateEvent(ircClient, messageEmitter, event)
28
+
29
+        assertEquals(1, events.size)
30
+        assertSame(event, events[0])
31
+    }
32
+
33
+    @Test
34
+    fun `starts a batch when BatchStarted event is received`() {
35
+        val event = BatchStarted(EventMetadata(TestConstants.time), "abcdef", "netsplit", arrayOf("foo", "bar"))
36
+        val events = mutator.mutateEvent(ircClient, messageEmitter, event)
37
+
38
+        assertTrue(events.isEmpty())
39
+        assertNotNull(serverState.batches["abcdef"])
40
+        serverState.batches["abcdef"]?.let {
41
+            assertEquals(listOf("foo", "bar"), it.arguments)
42
+            assertEquals("netsplit", it.type)
43
+            assertEquals(listOf(event), it.events)
44
+            assertNull(it.parent)
45
+        }
46
+    }
47
+
48
+    @Test
49
+    fun `adds to batch when event has a batch ID`() {
50
+        serverState.batches["abcdef"] = Batch("netsplit", emptyList())
51
+
52
+        val event = UserNickChanged(EventMetadata(TestConstants.time, "abcdef"), User("zeroCool"), "crashOverride")
53
+        mutator.mutateEvent(ircClient, messageEmitter, event)
54
+
55
+        assertEquals(listOf(event), serverState.batches["abcdef"]!!.events)
56
+    }
57
+
58
+    @Test
59
+    fun `suppresses event when it has a batch ID`() {
60
+        serverState.batches["abcdef"] = Batch("netsplit", emptyList())
61
+
62
+        val event = UserNickChanged(EventMetadata(TestConstants.time, "abcdef"), User("zeroCool"), "crashOverride")
63
+        val events = mutator.mutateEvent(ircClient, messageEmitter, event)
64
+
65
+        assertTrue(events.isEmpty())
66
+    }
67
+
68
+    @Test
69
+    fun `passes event for processing only when it has a batch ID`() {
70
+        serverState.batches["abcdef"] = Batch("netsplit", emptyList())
71
+
72
+        val event = UserNickChanged(EventMetadata(TestConstants.time, "abcdef"), User("zeroCool"), "crashOverride")
73
+        mutator.mutateEvent(ircClient, messageEmitter, event)
74
+
75
+        verify(messageEmitter).handleEvent(any(), same(event), eq(true))
76
+    }
77
+
78
+    @Test
79
+    fun `sends a batch when it finishes and the parent is null`() {
80
+        serverState.batches["abcdef"] = Batch("netsplit", listOf("p1", "p2"), events = mutableListOf(ServerConnected(EventMetadata(TestConstants.time, "abcdef"))))
81
+
82
+        val events = mutator.mutateEvent(ircClient, messageEmitter, BatchFinished(EventMetadata(TestConstants.time), "abcdef"))
83
+
84
+        assertEquals(1, events.size)
85
+        assertTrue(events[0] is BatchReceived)
86
+        val event = events[0] as BatchReceived
87
+        assertEquals("netsplit", event.type)
88
+        assertArrayEquals(arrayOf("p1", "p2"), event.params)
89
+        assertEquals(2, event.events.size)
90
+        assertTrue(event.events[0] is ServerConnected)
91
+        assertTrue(event.events[1] is BatchFinished)
92
+    }
93
+
94
+    @Test
95
+    fun `adds a batch to its parent when it finishes`() {
96
+        serverState.batches["12345"] = Batch("history", emptyList())
97
+        serverState.batches["abcdef"] = Batch("netsplit", emptyList(), "12345", mutableListOf(ServerConnected(EventMetadata(TestConstants.time, "abcdef"))))
98
+
99
+        val events = mutator.mutateEvent(ircClient, messageEmitter, BatchFinished(EventMetadata(TestConstants.time), "abcdef"))
100
+
101
+        assertEquals(0, events.size)
102
+
103
+        val parent = serverState.batches["12345"]?.events
104
+        assertEquals(1, parent?.size)
105
+
106
+        val event = parent?.get(0) as BatchReceived
107
+        assertEquals(2, event.events.size)
108
+        assertTrue(event.events[0] is ServerConnected)
109
+        assertTrue(event.events[1] is BatchFinished)
110
+    }
111
+
112
+    @Test
113
+    fun `deletes batch when it finishes`() {
114
+        serverState.batches["abcdef"] = Batch("netsplit", emptyList(), events = mutableListOf(ServerConnected(EventMetadata(TestConstants.time, "abcdef"))))
115
+
116
+        mutator.mutateEvent(ircClient, messageEmitter, BatchFinished(EventMetadata(TestConstants.time), "abcdef"))
117
+
118
+        assertNull(serverState.batches["abcdef"])
119
+    }
120
+
121
+}

+ 4
- 2
src/test/kotlin/com/dmdirc/ktirc/events/mutators/ChannelFanOutMutatorTest.kt View File

@@ -5,6 +5,7 @@ import com.dmdirc.ktirc.IrcClient
5 5
 import com.dmdirc.ktirc.TestConstants
6 6
 import com.dmdirc.ktirc.events.*
7 7
 import com.dmdirc.ktirc.io.CaseMapping
8
+import com.dmdirc.ktirc.io.MessageEmitter
8 9
 import com.dmdirc.ktirc.model.*
9 10
 import com.nhaarman.mockitokotlin2.doReturn
10 11
 import com.nhaarman.mockitokotlin2.mock
@@ -17,6 +18,7 @@ internal class ChannelFanOutMutatorTest {
17 18
     private val channelStateMap = ChannelStateMap { CaseMapping.Rfc }
18 19
     private val serverState = ServerState("", "")
19 20
     private val behaviour = BehaviourConfig()
21
+    private val messageEmitter = mock<MessageEmitter>()
20 22
     private val ircClient = mock<IrcClient> {
21 23
         on { serverState } doReturn serverState
22 24
         on { channelState } doReturn channelStateMap
@@ -43,7 +45,7 @@ internal class ChannelFanOutMutatorTest {
43 45
         }
44 46
 
45 47
         val quitEvent = UserQuit(EventMetadata(TestConstants.time), User("zerocool", "dade", "root.localhost"), "Hack the planet!")
46
-        val events = mutator.mutateEvent(ircClient, quitEvent)
48
+        val events = mutator.mutateEvent(ircClient, messageEmitter, quitEvent)
47 49
 
48 50
         val names = mutableListOf<String>()
49 51
         Assertions.assertEquals(3, events.size)
@@ -79,7 +81,7 @@ internal class ChannelFanOutMutatorTest {
79 81
         }
80 82
 
81 83
         val nickEvent = UserNickChanged(EventMetadata(TestConstants.time), User("zerocool", "dade", "root.localhost"), "zer0c00l")
82
-        val events = mutator.mutateEvent(ircClient, nickEvent)
84
+        val events = mutator.mutateEvent(ircClient, messageEmitter, nickEvent)
83 85
 
84 86
         val names = mutableListOf<String>()
85 87
         Assertions.assertEquals(3, events.size)

+ 4
- 2
src/test/kotlin/com/dmdirc/ktirc/events/mutators/ServerReadyMutatorTest.kt View File

@@ -3,6 +3,7 @@ package com.dmdirc.ktirc.events.mutators
3 3
 import com.dmdirc.ktirc.IrcClient
4 4
 import com.dmdirc.ktirc.TestConstants
5 5
 import com.dmdirc.ktirc.events.*
6
+import com.dmdirc.ktirc.io.MessageEmitter
6 7
 import com.dmdirc.ktirc.model.ServerState
7 8
 import com.dmdirc.ktirc.model.ServerStatus
8 9
 import com.dmdirc.ktirc.model.User
@@ -14,6 +15,7 @@ import org.junit.jupiter.api.Test
14 15
 internal class ServerReadyMutatorTest {
15 16
 
16 17
     private val serverState = ServerState("", "")
18
+    private val messageEmitter = mock<MessageEmitter>()
17 19
     private val ircClient = mock<IrcClient> {
18 20
         on { serverState } doReturn serverState
19 21
     }
@@ -33,11 +35,11 @@ internal class ServerReadyMutatorTest {
33 35
                 ServerCapabilitiesAcknowledged(EventMetadata(TestConstants.time), emptyMap()),
34 36
                 ServerCapabilitiesFinished(EventMetadata(TestConstants.time))
35 37
         ).forEach {
36
-            assertEquals(1, mutator.mutateEvent(ircClient, it).size)
38
+            assertEquals(1, mutator.mutateEvent(ircClient, messageEmitter, it).size)
37 39
         }
38 40
 
39 41
         val event = MessageReceived(EventMetadata(TestConstants.time), User("zeroCool"), "acidBurn", "Welcome!")
40
-        val events = mutator.mutateEvent(ircClient, event)
42
+        val events = mutator.mutateEvent(ircClient, messageEmitter, event)
41 43
         assertEquals(2, events.size)
42 44
         assertSame(event, events[1])
43 45
         assertTrue(events[0] is ServerReady)

+ 48
- 21
src/test/kotlin/com/dmdirc/ktirc/io/MessageHandlerTest.kt View File

@@ -2,10 +2,7 @@ package com.dmdirc.ktirc.io
2 2
 
3 3
 import com.dmdirc.ktirc.IrcClient
4 4
 import com.dmdirc.ktirc.TestConstants
5
-import com.dmdirc.ktirc.events.EventMetadata
6
-import com.dmdirc.ktirc.events.ServerConnected
7
-import com.dmdirc.ktirc.events.ServerReady
8
-import com.dmdirc.ktirc.events.ServerWelcome
5
+import com.dmdirc.ktirc.events.*
9 6
 import com.dmdirc.ktirc.events.handlers.EventHandler
10 7
 import com.dmdirc.ktirc.events.mutators.EventMutator
11 8
 import com.dmdirc.ktirc.messages.MessageProcessor
@@ -83,31 +80,61 @@ internal class MessageHandlerTest {
83 80
     }
84 81
 
85 82
     @Test
86
-    fun `emits custom events to all handlers`() {
83
+    fun `sends custom events to all handlers`() {
87 84
         val eventHandler1 = mock<EventHandler>()
88 85
         val eventHandler2 = mock<EventHandler>()
89 86
         val handler = MessageHandler(emptyList(), emptyList(), mutableListOf(eventHandler1, eventHandler2))
90
-        handler.emitEvent(ircClient, ServerWelcome(EventMetadata(TestConstants.time), "the.gibson", "acidBurn"))
87
+        handler.handleEvent(ircClient, ServerWelcome(EventMetadata(TestConstants.time), "the.gibson", "acidBurn"))
91 88
 
92 89
         verify(eventHandler1).processEvent(same(ircClient), isA<ServerWelcome>())
93 90
         verify(eventHandler2).processEvent(same(ircClient), isA<ServerWelcome>())
94 91
     }
95 92
 
93
+    @Test
94
+    fun `sends custom events to all emitters`() {
95
+        val handler = MessageHandler(emptyList(), emptyList(), emptyList())
96
+        val emitter1 = mock<(IrcEvent) -> Unit>()
97
+        val emitter2 = mock<(IrcEvent) -> Unit>()
98
+        handler.addEmitter(emitter1)
99
+        handler.addEmitter(emitter2)
100
+        handler.handleEvent(ircClient, ServerWelcome(EventMetadata(TestConstants.time), "the.gibson", "acidBurn"))
101
+
102
+        verify(emitter1).invoke(isA<ServerWelcome>())
103
+        verify(emitter2).invoke(isA<ServerWelcome>())
104
+    }
105
+
106
+    @Test
107
+    fun `sends events to handlers but not mutators or emitters if process only is true`() {
108
+        val mutator = mock<EventMutator>()
109
+        val eventHandler1 = mock<EventHandler>()
110
+        val eventHandler2 = mock<EventHandler>()
111
+        val handler = MessageHandler(emptyList(), listOf(mutator), listOf(eventHandler1, eventHandler2))
112
+        val emitter = mock<(IrcEvent) -> Unit>()
113
+        handler.addEmitter(emitter)
114
+
115
+        handler.handleEvent(ircClient, ServerWelcome(EventMetadata(TestConstants.time), "the.gibson", "acidBurn"), true)
116
+
117
+        verify(eventHandler1).processEvent(same(ircClient), isA<ServerWelcome>())
118
+        verify(eventHandler2).processEvent(same(ircClient), isA<ServerWelcome>())
119
+        verify(emitter, never()).invoke(any())
120
+        verify(mutator, never()).mutateEvent(any(), any(), any())
121
+    }
122
+
96 123
     @Test
97 124
     fun `mutates events in order`() {
98 125
         val eventMutator1 = mock<EventMutator> {
99
-            on { mutateEvent(any(), isA<ServerWelcome>()) } doReturn listOf(ServerReady(EventMetadata(TestConstants.time)))
126
+            on { mutateEvent(any(), any(), isA<ServerWelcome>()) } doReturn listOf(ServerReady(EventMetadata(TestConstants.time)))
100 127
         }
101 128
         val eventMutator2 = mock<EventMutator> {
102
-            on { mutateEvent(any(), isA<ServerReady>()) } doReturn listOf(ServerConnected(EventMetadata(TestConstants.time)))
129
+            on { mutateEvent(any(), any(), isA<ServerReady>()) } doReturn listOf(ServerConnected(EventMetadata(TestConstants.time)))
103 130
         }
104 131
         val eventHandler = mock<EventHandler>()
105 132
 
106 133
         val handler = MessageHandler(emptyList(), listOf(eventMutator1, eventMutator2), mutableListOf(eventHandler))
107
-        handler.emitEvent(ircClient, ServerWelcome(EventMetadata(TestConstants.time), "the.gibson", "acidBurn"))
134
+        handler.handleEvent(ircClient, ServerWelcome(EventMetadata(TestConstants.time), "the.gibson", "acidBurn"))
108 135
 
109
-        verify(eventMutator1).mutateEvent(same(ircClient), isA<ServerWelcome>())
110
-        verify(eventMutator2).mutateEvent(same(ircClient), isA<ServerReady>())
136
+        verify(eventMutator1).mutateEvent(same(ircClient), same(handler), isA<ServerWelcome>())
137
+        verify(eventMutator2).mutateEvent(same(ircClient), same(handler), isA<ServerReady>())
111 138
         verify(eventHandler).processEvent(same(ircClient), isA<ServerConnected>())
112 139
         verifyNoMoreInteractions(eventHandler)
113 140
     }
@@ -115,23 +142,23 @@ internal class MessageHandlerTest {
115 142
     @Test
116 143
     fun `allows mutators to fan out events`() {
117 144
         val eventMutator1 = mock<EventMutator> {
118
-            on { mutateEvent(any(), isA<ServerWelcome>()) } doReturn listOf(
145
+            on { mutateEvent(any(), any(), isA<ServerWelcome>()) } doReturn listOf(
119 146
                     ServerReady(EventMetadata(TestConstants.time)),
120 147
                     ServerConnected(EventMetadata(TestConstants.time))
121 148
             )
122 149
         }
123 150
         val eventMutator2 = mock<EventMutator> {
124
-            on { mutateEvent(any(), isA<ServerReady>()) } doReturn listOf(ServerReady(EventMetadata(TestConstants.time)))
125
-            on { mutateEvent(any(), isA<ServerConnected>()) } doReturn listOf(ServerConnected(EventMetadata(TestConstants.time)))
151
+            on { mutateEvent(any(), any(), isA<ServerReady>()) } doReturn listOf(ServerReady(EventMetadata(TestConstants.time)))
152
+            on { mutateEvent(any(), any(), isA<ServerConnected>()) } doReturn listOf(ServerConnected(EventMetadata(TestConstants.time)))
126 153
         }
127 154
         val eventHandler = mock<EventHandler>()
128 155
 
129 156
         val handler = MessageHandler(emptyList(), listOf(eventMutator1, eventMutator2), mutableListOf(eventHandler))
130
-        handler.emitEvent(ircClient, ServerWelcome(EventMetadata(TestConstants.time), "the.gibson", "acidBurn"))
157
+        handler.handleEvent(ircClient, ServerWelcome(EventMetadata(TestConstants.time), "the.gibson", "acidBurn"))
131 158
 
132
-        with (inOrder(eventMutator2, eventHandler)) {
133
-            verify(eventMutator2).mutateEvent(same(ircClient), isA<ServerReady>())
134
-            verify(eventMutator2).mutateEvent(same(ircClient), isA<ServerConnected>())
159
+        with(inOrder(eventMutator2, eventHandler)) {
160
+            verify(eventMutator2).mutateEvent(same(ircClient), same(handler), isA<ServerReady>())
161
+            verify(eventMutator2).mutateEvent(same(ircClient), same(handler), isA<ServerConnected>())
135 162
             verify(eventHandler).processEvent(same(ircClient), isA<ServerReady>())
136 163
             verify(eventHandler).processEvent(same(ircClient), isA<ServerConnected>())
137 164
         }
@@ -140,15 +167,15 @@ internal class MessageHandlerTest {
140 167
     @Test
141 168
     fun `allows mutators to suppress events`() {
142 169
         val eventMutator1 = mock<EventMutator> {
143
-            on { mutateEvent(any(), isA<ServerWelcome>()) } doReturn emptyList()
170
+            on { mutateEvent(any(), any(), isA<ServerWelcome>()) } doReturn emptyList()
144 171
         }
145 172
         val eventMutator2 = mock<EventMutator>()
146 173
         val eventHandler = mock<EventHandler>()
147 174
 
148 175
         val handler = MessageHandler(emptyList(), listOf(eventMutator1, eventMutator2), mutableListOf(eventHandler))
149
-        handler.emitEvent(ircClient, ServerWelcome(EventMetadata(TestConstants.time), "the.gibson", "acidBurn"))
176
+        handler.handleEvent(ircClient, ServerWelcome(EventMetadata(TestConstants.time), "the.gibson", "acidBurn"))
150 177
 
151
-        verify(eventMutator2, never()).mutateEvent(any(), any())
178
+        verify(eventMutator2, never()).mutateEvent(any(), same(handler), any())
152 179
         verify(eventHandler, never()).processEvent(any(), any())
153 180
     }
154 181
 

+ 52
- 0
src/test/kotlin/com/dmdirc/ktirc/messages/BatchProcessorTest.kt View File

@@ -0,0 +1,52 @@
1
+package com.dmdirc.ktirc.messages
2
+
3
+import com.dmdirc.ktirc.TestConstants
4
+import com.dmdirc.ktirc.events.BatchFinished
5
+import com.dmdirc.ktirc.events.BatchStarted
6
+import com.dmdirc.ktirc.model.IrcMessage
7
+import com.dmdirc.ktirc.params
8
+import com.dmdirc.ktirc.util.currentTimeProvider
9
+import org.junit.jupiter.api.Assertions.assertArrayEquals
10
+import org.junit.jupiter.api.Assertions.assertEquals
11
+import org.junit.jupiter.api.BeforeEach
12
+import org.junit.jupiter.api.Test
13
+
14
+internal class BatchProcessorTest {
15
+
16
+    private var processor = BatchProcessor()
17
+
18
+    @BeforeEach
19
+    fun setUp() {
20
+        currentTimeProvider = { TestConstants.time }
21
+    }
22
+
23
+    @Test
24
+    fun `raises batch finished event when reference starts with a -`() {
25
+        val events = processor.process(IrcMessage(emptyMap(), null, "BATCH", params("-mybatch")))
26
+
27
+        assertEquals(1, events.size)
28
+        val event = events[0] as BatchFinished
29
+        assertEquals(TestConstants.time, event.metadata.time)
30
+        assertEquals("mybatch", event.referenceId)
31
+    }
32
+
33
+    @Test
34
+    fun `raises batch started event when reference starts with a +`() {
35
+        val events = processor.process(IrcMessage(emptyMap(), null, "BATCH", params("+mybatch", "mytype", "arg1", "arg2")))
36
+
37
+        assertEquals(1, events.size)
38
+        val event = events[0] as BatchStarted
39
+        assertEquals(TestConstants.time, event.metadata.time)
40
+        assertEquals("mybatch", event.referenceId)
41
+        assertEquals("mytype", event.batchType)
42
+        assertArrayEquals(arrayOf("arg1", "arg2"), event.params)
43
+    }
44
+
45
+    @Test
46
+    fun `ignores batches with bad reference ids`() {
47
+        val events = processor.process(IrcMessage(emptyMap(), null, "BATCH", params("~mybatch")))
48
+
49
+        assertEquals(0, events.size)
50
+    }
51
+
52
+}

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

@@ -67,6 +67,7 @@ internal class ServerStateTest {
67 67
         features[ServerFeature.Network] = "gibson"
68 68
         capabilities.advertisedCapabilities[Capability.SaslAuthentication] = "sure"
69 69
         sasl.saslBuffer = "in progress"
70
+        batches["batch"] = Batch("type", emptyList())
70 71
 
71 72
         reset()
72 73
 
@@ -77,6 +78,7 @@ internal class ServerStateTest {
77 78
         assertTrue(features.isEmpty())
78 79
         assertTrue(capabilities.advertisedCapabilities.isEmpty())
79 80
         assertEquals("", sasl.saslBuffer)
81
+        assertTrue(batches.isEmpty())
80 82
     }
81 83
 
82 84
 }

Loading…
Cancel
Save