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
  * Events now have a `metadata` property instead of a `time` (and time is available in metadata)
7
  * Events now have a `metadata` property instead of a `time` (and time is available in metadata)
8
    * IrcEvent.time is now deprecated but will remain until after v1.0.0.
8
    * IrcEvent.time is now deprecated but will remain until after v1.0.0.
9
    * Metadata now contains the event's batch ID, if any.
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
  * (Internal) Introduced event mutators
13
  * (Internal) Introduced event mutators
11
    * Event mutators are now responsible for handling changing events in response to state
14
    * Event mutators are now responsible for handling changing events in response to state
12
      e.g. ChannelFanOutMutator creates Channel* events for global quits/nick changes/etc
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
 package com.dmdirc.ktirc
1
 package com.dmdirc.ktirc
2
 
2
 
3
 import com.dmdirc.ktirc.events.*
3
 import com.dmdirc.ktirc.events.*
4
-import com.dmdirc.ktirc.events.handlers.EventHandler
5
 import com.dmdirc.ktirc.events.handlers.eventHandlers
4
 import com.dmdirc.ktirc.events.handlers.eventHandlers
6
 import com.dmdirc.ktirc.events.mutators.eventMutators
5
 import com.dmdirc.ktirc.events.mutators.eventMutators
7
 import com.dmdirc.ktirc.io.KtorLineBufferedSocket
6
 import com.dmdirc.ktirc.io.KtorLineBufferedSocket
42
     override val channelState = ChannelStateMap { caseMapping }
41
     override val channelState = ChannelStateMap { caseMapping }
43
     override val userState = UserState { caseMapping }
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
     private val parser = MessageParser()
46
     private val parser = MessageParser()
48
     private var socket: LineBufferedSocket? = null
47
     private var socket: LineBufferedSocket? = null
85
         socket?.disconnect()
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
     private fun sendPasswordIfPresent() = config.server.password?.let(this::sendPassword)
90
     private fun sendPasswordIfPresent() = config.server.password?.let(this::sendPassword)
98
 
91
 
99
     internal fun reset() {
92
     internal fun reset() {

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

152
 
152
 
153
 /** Indicates a batch of messages has finished. */
153
 /** Indicates a batch of messages has finished. */
154
 class BatchFinished(metadata: EventMetadata, val referenceId: String) : IrcEvent(metadata)
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

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
 
2
 
3
 import com.dmdirc.ktirc.IrcClient
3
 import com.dmdirc.ktirc.IrcClient
4
 import com.dmdirc.ktirc.events.*
4
 import com.dmdirc.ktirc.events.*
5
+import com.dmdirc.ktirc.io.MessageEmitter
5
 
6
 
6
 /**
7
 /**
7
  * "Fans out" global events such as quits and nick changes to each channel a user is in.
8
  * "Fans out" global events such as quits and nick changes to each channel a user is in.
8
  */
9
  */
9
 internal class ChannelFanOutMutator : EventMutator {
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
         yield(event)
13
         yield(event)
13
         when (event) {
14
         when (event) {
14
             is UserQuit -> handleQuit(client, event)
15
             is UserQuit -> handleQuit(client, event)

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

2
 
2
 
3
 import com.dmdirc.ktirc.IrcClient
3
 import com.dmdirc.ktirc.IrcClient
4
 import com.dmdirc.ktirc.events.IrcEvent
4
 import com.dmdirc.ktirc.events.IrcEvent
5
+import com.dmdirc.ktirc.io.MessageEmitter
5
 
6
 
6
 @FunctionalInterface
7
 @FunctionalInterface
7
 internal interface EventMutator {
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
 internal val eventMutators = listOf(
14
 internal val eventMutators = listOf(
14
         ServerReadyMutator(),
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
 
2
 
3
 import com.dmdirc.ktirc.IrcClient
3
 import com.dmdirc.ktirc.IrcClient
4
 import com.dmdirc.ktirc.events.*
4
 import com.dmdirc.ktirc.events.*
5
+import com.dmdirc.ktirc.io.MessageEmitter
5
 import com.dmdirc.ktirc.model.ServerStatus
6
 import com.dmdirc.ktirc.model.ServerStatus
6
 
7
 
7
 /**
8
 /**
24
             ServerCapabilitiesFinished::class
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
         if (client.serverState.receivedWelcome
29
         if (client.serverState.receivedWelcome
29
                 && client.serverState.status == ServerStatus.Negotiating
30
                 && client.serverState.status == ServerStatus.Negotiating
30
                 && event::class !in excludedEvents) {
31
                 && event::class !in excludedEvents) {

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

9
 import com.dmdirc.ktirc.util.logger
9
 import com.dmdirc.ktirc.util.logger
10
 import kotlinx.coroutines.channels.ReceiveChannel
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
 internal class MessageHandler(
20
 internal class MessageHandler(
13
         private val processors: List<MessageProcessor>,
21
         private val processors: List<MessageProcessor>,
14
         private val mutators: List<EventMutator>,
22
         private val mutators: List<EventMutator>,
15
-        val handlers: MutableList<EventHandler>) {
23
+        private val handlers: List<EventHandler>) : MessageEmitter {
16
 
24
 
17
     private val log by logger()
25
     private val log by logger()
18
 
26
 
27
+    private val emitters = mutableListOf<(IrcEvent) -> Unit>()
28
+
19
     suspend fun processMessages(ircClient: IrcClient, messages: ReceiveChannel<IrcMessage>) {
29
     suspend fun processMessages(ircClient: IrcClient, messages: ReceiveChannel<IrcMessage>) {
20
         for (message in messages) {
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
     private fun IrcMessage.toEvents() = this.getProcessor()?.process(this) ?: emptyList()
56
     private fun IrcMessage.toEvents() = this.getProcessor()?.process(this) ?: emptyList()
37
     private fun IrcMessage.getProcessor() = processors.firstOrNull { it.commands.contains(command) } ?: run {
57
     private fun IrcMessage.getProcessor() = processors.firstOrNull { it.commands.contains(command) } ?: run {
38
         log.warning { "No processor found for $command" }
58
         log.warning { "No processor found for $command" }

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

1
 package com.dmdirc.ktirc.messages
1
 package com.dmdirc.ktirc.messages
2
 
2
 
3
+import com.dmdirc.ktirc.events.BatchFinished
4
+import com.dmdirc.ktirc.events.BatchStarted
3
 import com.dmdirc.ktirc.events.IrcEvent
5
 import com.dmdirc.ktirc.events.IrcEvent
4
 import com.dmdirc.ktirc.model.IrcMessage
6
 import com.dmdirc.ktirc.model.IrcMessage
5
 
7
 
8
     override val commands = arrayOf("BATCH")
10
     override val commands = arrayOf("BATCH")
9
 
11
 
10
     override fun process(message: IrcMessage): List<IrcEvent> {
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
 internal val messageProcessors = listOf(
20
 internal val messageProcessors = listOf(
21
         AccountProcessor(),
21
         AccountProcessor(),
22
         AuthenticationProcessor(),
22
         AuthenticationProcessor(),
23
+        BatchProcessor(),
23
         CapabilityProcessor(),
24
         CapabilityProcessor(),
24
         ISupportProcessor(),
25
         ISupportProcessor(),
25
         JoinProcessor(),
26
         JoinProcessor(),

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

1
 package com.dmdirc.ktirc.model
1
 package com.dmdirc.ktirc.model
2
 
2
 
3
 import com.dmdirc.ktirc.SaslConfig
3
 import com.dmdirc.ktirc.SaslConfig
4
+import com.dmdirc.ktirc.events.IrcEvent
4
 import com.dmdirc.ktirc.io.CaseMapping
5
 import com.dmdirc.ktirc.io.CaseMapping
5
 import com.dmdirc.ktirc.util.logger
6
 import com.dmdirc.ktirc.util.logger
6
 import kotlin.reflect.KClass
7
 import kotlin.reflect.KClass
63
     val channelTypes
64
     val channelTypes
64
         get() = features[ServerFeature.ChannelTypes] ?: throw IllegalStateException("lost channel types")
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
      * Determines if the given mode is one applied to a user of a channel, such as 'o' for operator.
73
      * Determines if the given mode is one applied to a user of a channel, such as 'o' for operator.
68
      */
74
      */
92
         features.clear()
98
         features.clear()
93
         capabilities.reset()
99
         capabilities.reset()
94
         sasl.reset()
100
         sasl.reset()
101
+        batches.clear()
95
     }
102
     }
96
 
103
 
97
 }
104
 }
176
     Ready,
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
 internal val serverFeatures: Map<String, ServerFeature<*>> by lazy {
191
 internal val serverFeatures: Map<String, ServerFeature<*>> by lazy {
180
     ServerFeature::class.nestedClasses.map { it.objectInstance as ServerFeature<*> }.associateBy { it.name }
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

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

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

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

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

2
 
2
 
3
 import com.dmdirc.ktirc.IrcClient
3
 import com.dmdirc.ktirc.IrcClient
4
 import com.dmdirc.ktirc.TestConstants
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
 import com.dmdirc.ktirc.events.handlers.EventHandler
6
 import com.dmdirc.ktirc.events.handlers.EventHandler
10
 import com.dmdirc.ktirc.events.mutators.EventMutator
7
 import com.dmdirc.ktirc.events.mutators.EventMutator
11
 import com.dmdirc.ktirc.messages.MessageProcessor
8
 import com.dmdirc.ktirc.messages.MessageProcessor
83
     }
80
     }
84
 
81
 
85
     @Test
82
     @Test
86
-    fun `emits custom events to all handlers`() {
83
+    fun `sends custom events to all handlers`() {
87
         val eventHandler1 = mock<EventHandler>()
84
         val eventHandler1 = mock<EventHandler>()
88
         val eventHandler2 = mock<EventHandler>()
85
         val eventHandler2 = mock<EventHandler>()
89
         val handler = MessageHandler(emptyList(), emptyList(), mutableListOf(eventHandler1, eventHandler2))
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
         verify(eventHandler1).processEvent(same(ircClient), isA<ServerWelcome>())
89
         verify(eventHandler1).processEvent(same(ircClient), isA<ServerWelcome>())
93
         verify(eventHandler2).processEvent(same(ircClient), isA<ServerWelcome>())
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
     @Test
123
     @Test
97
     fun `mutates events in order`() {
124
     fun `mutates events in order`() {
98
         val eventMutator1 = mock<EventMutator> {
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
         val eventMutator2 = mock<EventMutator> {
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
         val eventHandler = mock<EventHandler>()
131
         val eventHandler = mock<EventHandler>()
105
 
132
 
106
         val handler = MessageHandler(emptyList(), listOf(eventMutator1, eventMutator2), mutableListOf(eventHandler))
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
         verify(eventHandler).processEvent(same(ircClient), isA<ServerConnected>())
138
         verify(eventHandler).processEvent(same(ircClient), isA<ServerConnected>())
112
         verifyNoMoreInteractions(eventHandler)
139
         verifyNoMoreInteractions(eventHandler)
113
     }
140
     }
115
     @Test
142
     @Test
116
     fun `allows mutators to fan out events`() {
143
     fun `allows mutators to fan out events`() {
117
         val eventMutator1 = mock<EventMutator> {
144
         val eventMutator1 = mock<EventMutator> {
118
-            on { mutateEvent(any(), isA<ServerWelcome>()) } doReturn listOf(
145
+            on { mutateEvent(any(), any(), isA<ServerWelcome>()) } doReturn listOf(
119
                     ServerReady(EventMetadata(TestConstants.time)),
146
                     ServerReady(EventMetadata(TestConstants.time)),
120
                     ServerConnected(EventMetadata(TestConstants.time))
147
                     ServerConnected(EventMetadata(TestConstants.time))
121
             )
148
             )
122
         }
149
         }
123
         val eventMutator2 = mock<EventMutator> {
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
         val eventHandler = mock<EventHandler>()
154
         val eventHandler = mock<EventHandler>()
128
 
155
 
129
         val handler = MessageHandler(emptyList(), listOf(eventMutator1, eventMutator2), mutableListOf(eventHandler))
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
             verify(eventHandler).processEvent(same(ircClient), isA<ServerReady>())
162
             verify(eventHandler).processEvent(same(ircClient), isA<ServerReady>())
136
             verify(eventHandler).processEvent(same(ircClient), isA<ServerConnected>())
163
             verify(eventHandler).processEvent(same(ircClient), isA<ServerConnected>())
137
         }
164
         }
140
     @Test
167
     @Test
141
     fun `allows mutators to suppress events`() {
168
     fun `allows mutators to suppress events`() {
142
         val eventMutator1 = mock<EventMutator> {
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
         val eventMutator2 = mock<EventMutator>()
172
         val eventMutator2 = mock<EventMutator>()
146
         val eventHandler = mock<EventHandler>()
173
         val eventHandler = mock<EventHandler>()
147
 
174
 
148
         val handler = MessageHandler(emptyList(), listOf(eventMutator1, eventMutator2), mutableListOf(eventHandler))
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
         verify(eventHandler, never()).processEvent(any(), any())
179
         verify(eventHandler, never()).processEvent(any(), any())
153
     }
180
     }
154
 
181
 

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

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

Loading…
Cancel
Save