Browse Source

Make various processors more defensive

tags/v0.10.3
Chris Smith 5 years ago
parent
commit
1b135480c9

+ 6
- 1
CHANGELOG View File

1
 vNEXT (in development)
1
 vNEXT (in development)
2
 
2
 
3
  * Fix issue parsing CTCPs when the content contained multi-byte chars
3
  * Fix issue parsing CTCPs when the content contained multi-byte chars
4
- * If the server provides an invalid timestamp, the event now defaults to the current time
4
+ * Fixed multiple issues when receiving malformed input from the server:
5
+   * Invalid server-time tags are now ignored and the current time used
6
+   * PRIVMSGs, NOTICEs and TOPICs with no target/message are now ignored
7
+   * CAPs with invalid or missing arguments are now ignored
8
+   * Better handling for duplicate JOIN messages
9
+   * Ignored messages are now logged more consistently
5
 
10
 
6
 v0.10.2
11
 v0.10.2
7
 
12
 

+ 11
- 4
src/main/kotlin/com/dmdirc/ktirc/messages/processors/CapabilityProcessor.kt View File

14
 
14
 
15
     override val commands = arrayOf("CAP")
15
     override val commands = arrayOf("CAP")
16
 
16
 
17
-    override fun process(message: IrcMessage) = when (message.subCommand) {
18
-        "LS" -> handleList(message.metadata, message.subCommandArguments)
19
-        "ACK" -> listOf(ServerCapabilitiesAcknowledged(message.metadata, message.params.capabilities))
20
-        else -> emptyList()
17
+    override fun process(message: IrcMessage) = when {
18
+        message.params.size < 2 -> {
19
+            log.warning { "Discarding CAP with insufficient args: $message" }
20
+            emptyList()
21
+        }
22
+        message.subCommand == "LS" -> handleList(message.metadata, message.subCommandArguments)
23
+        message.subCommand == "ACK" -> listOf(ServerCapabilitiesAcknowledged(message.metadata, message.params.capabilities))
24
+        else -> {
25
+            log.warning { "Discarding CAP with unknown subcommand: $message" }
26
+            emptyList()
27
+        }
21
     }
28
     }
22
 
29
 
23
     private fun handleList(metadata: EventMetadata, lsParams: List<ByteArray>) = sequence {
30
     private fun handleList(metadata: EventMetadata, lsParams: List<ByteArray>) = sequence {

+ 9
- 0
src/main/kotlin/com/dmdirc/ktirc/messages/processors/ModeProcessor.kt View File

4
 import com.dmdirc.ktirc.messages.RPL_CHANNELMODEIS
4
 import com.dmdirc.ktirc.messages.RPL_CHANNELMODEIS
5
 import com.dmdirc.ktirc.messages.RPL_UMODEIS
5
 import com.dmdirc.ktirc.messages.RPL_UMODEIS
6
 import com.dmdirc.ktirc.model.IrcMessage
6
 import com.dmdirc.ktirc.model.IrcMessage
7
+import com.dmdirc.ktirc.util.logger
7
 
8
 
8
 internal class ModeProcessor : MessageProcessor {
9
 internal class ModeProcessor : MessageProcessor {
9
 
10
 
11
+    private val log by logger()
12
+
10
     override val commands = arrayOf(RPL_CHANNELMODEIS, RPL_UMODEIS, "MODE")
13
     override val commands = arrayOf(RPL_CHANNELMODEIS, RPL_UMODEIS, "MODE")
11
 
14
 
12
     override fun process(message: IrcMessage): List<ModeChanged> {
15
     override fun process(message: IrcMessage): List<ModeChanged> {
13
         val isDiscovery = message.command == RPL_CHANNELMODEIS || message.command == RPL_UMODEIS
16
         val isDiscovery = message.command == RPL_CHANNELMODEIS || message.command == RPL_UMODEIS
14
         val paramOffset = if (message.command == RPL_CHANNELMODEIS) 1 else 0
17
         val paramOffset = if (message.command == RPL_CHANNELMODEIS) 1 else 0
18
+
19
+        if (message.params.size < paramOffset + 2) {
20
+            log.warning { "Discarding MODE line with insufficient parameters: $message" }
21
+            return emptyList()
22
+        }
23
+
15
         return listOf(ModeChanged(
24
         return listOf(ModeChanged(
16
                 message.metadata,
25
                 message.metadata,
17
                 target = String(message.params[paramOffset]),
26
                 target = String(message.params[paramOffset]),

+ 11
- 3
src/main/kotlin/com/dmdirc/ktirc/messages/processors/NoticeProcessor.kt View File

6
 import com.dmdirc.ktirc.messages.CTCP_BYTE
6
 import com.dmdirc.ktirc.messages.CTCP_BYTE
7
 import com.dmdirc.ktirc.model.IrcMessage
7
 import com.dmdirc.ktirc.model.IrcMessage
8
 import com.dmdirc.ktirc.model.User
8
 import com.dmdirc.ktirc.model.User
9
+import com.dmdirc.ktirc.util.logger
9
 
10
 
10
 internal class NoticeProcessor : MessageProcessor {
11
 internal class NoticeProcessor : MessageProcessor {
11
 
12
 
13
+    private val log by logger()
14
+
12
     override val commands = arrayOf("NOTICE")
15
     override val commands = arrayOf("NOTICE")
13
 
16
 
14
     override fun process(message: IrcMessage) = when {
17
     override fun process(message: IrcMessage) = when {
15
-            message.isCtcp() -> handleCtcp(message,  message.sourceUser)
16
-            else -> listOf(NoticeReceived(message.metadata,  message.sourceUser ?: User("*"), String(message.params[0]), String(message.params[1])))
18
+        message.params.size < 2 -> {
19
+            log.warning { "Discarding NOTICE line with insufficient parameters: $message" }
20
+            emptyList()
17
         }
21
         }
22
+        message.isCtcp() -> handleCtcp(message, message.sourceUser)
23
+        else -> listOf(NoticeReceived(message.metadata, message.sourceUser
24
+                ?: User("*"), String(message.params[0]), String(message.params[1])))
25
+    }
18
 
26
 
19
     private fun handleCtcp(message: IrcMessage, user: User?): List<IrcEvent> {
27
     private fun handleCtcp(message: IrcMessage, user: User?): List<IrcEvent> {
20
         user ?: return emptyList()
28
         user ?: return emptyList()
21
         val content = String(message.params[1].sliceArray(1 until message.params[1].size - 1))
29
         val content = String(message.params[1].sliceArray(1 until message.params[1].size - 1))
22
-        val parts = content.split(' ', limit=2)
30
+        val parts = content.split(' ', limit = 2)
23
         val body = if (parts.size == 2) parts[1] else ""
31
         val body = if (parts.size == 2) parts[1] else ""
24
         return listOf(CtcpReplyReceived(message.metadata, user, String(message.params[0]), parts[0], body))
32
         return listOf(CtcpReplyReceived(message.metadata, user, String(message.params[0]), parts[0], body))
25
     }
33
     }

+ 13
- 7
src/main/kotlin/com/dmdirc/ktirc/messages/processors/PrivmsgProcessor.kt View File

7
 import com.dmdirc.ktirc.messages.CTCP_BYTE
7
 import com.dmdirc.ktirc.messages.CTCP_BYTE
8
 import com.dmdirc.ktirc.model.IrcMessage
8
 import com.dmdirc.ktirc.model.IrcMessage
9
 import com.dmdirc.ktirc.model.User
9
 import com.dmdirc.ktirc.model.User
10
+import com.dmdirc.ktirc.util.logger
10
 
11
 
11
 internal class PrivmsgProcessor : MessageProcessor {
12
 internal class PrivmsgProcessor : MessageProcessor {
12
 
13
 
14
+    private val log by logger()
15
+
13
     override val commands = arrayOf("PRIVMSG")
16
     override val commands = arrayOf("PRIVMSG")
14
 
17
 
15
-    override fun process(message: IrcMessage) = message.sourceUser?.let { user ->
16
-        listOf(when {
17
-            message.isCtcp() -> handleCtcp(message, user)
18
-            else -> MessageReceived(message.metadata, user, String(message.params[0]), String(message.params[1]))
19
-        })
20
-    } ?: emptyList()
18
+    override fun process(message: IrcMessage) = when {
19
+        message.sourceUser == null -> emptyList()
20
+        message.params.size < 2 -> {
21
+            log.warning { "Discarding PRIVMSG line with insufficient parameters: $message" }
22
+            emptyList()
23
+        }
24
+        message.isCtcp() -> listOf(handleCtcp(message, message.sourceUser))
25
+        else -> listOf(MessageReceived(message.metadata, message.sourceUser, String(message.params[0]), String(message.params[1])))
26
+    }
21
 
27
 
22
     private fun handleCtcp(message: IrcMessage, user: User): IrcEvent {
28
     private fun handleCtcp(message: IrcMessage, user: User): IrcEvent {
23
         val content = String(message.params[1].sliceArray(1 until message.params[1].size - 1))
29
         val content = String(message.params[1].sliceArray(1 until message.params[1].size - 1))
24
-        val parts = content.split(' ', limit=2)
30
+        val parts = content.split(' ', limit = 2)
25
         val body = if (parts.size == 2) parts[1] else ""
31
         val body = if (parts.size == 2) parts[1] else ""
26
         return when (parts[0].toUpperCase()) {
32
         return when (parts[0].toUpperCase()) {
27
             "ACTION" -> ActionReceived(message.metadata, user, String(message.params[0]), body)
33
             "ACTION" -> ActionReceived(message.metadata, user, String(message.params[0]), body)

+ 10
- 1
src/main/kotlin/com/dmdirc/ktirc/messages/processors/TopicProcessor.kt View File

9
 import com.dmdirc.ktirc.model.IrcMessage
9
 import com.dmdirc.ktirc.model.IrcMessage
10
 import com.dmdirc.ktirc.model.asUser
10
 import com.dmdirc.ktirc.model.asUser
11
 import com.dmdirc.ktirc.util.currentTimeZoneProvider
11
 import com.dmdirc.ktirc.util.currentTimeZoneProvider
12
+import com.dmdirc.ktirc.util.logger
12
 import java.time.Instant
13
 import java.time.Instant
13
 import java.time.LocalDateTime
14
 import java.time.LocalDateTime
14
 
15
 
15
 internal class TopicProcessor : MessageProcessor {
16
 internal class TopicProcessor : MessageProcessor {
16
 
17
 
18
+    private val log by logger()
19
+
17
     override val commands = arrayOf(RPL_TOPIC, RPL_TOPICWHOTIME, RPL_NOTOPIC, "TOPIC")
20
     override val commands = arrayOf(RPL_TOPIC, RPL_TOPICWHOTIME, RPL_NOTOPIC, "TOPIC")
18
 
21
 
19
     override fun process(message: IrcMessage) = sequence {
22
     override fun process(message: IrcMessage) = sequence {
22
             RPL_NOTOPIC -> yield(ChannelTopicDiscovered(message.metadata, message.channel, null))
25
             RPL_NOTOPIC -> yield(ChannelTopicDiscovered(message.metadata, message.channel, null))
23
             RPL_TOPICWHOTIME -> yield(ChannelTopicMetadataDiscovered(
26
             RPL_TOPICWHOTIME -> yield(ChannelTopicMetadataDiscovered(
24
                     message.metadata, message.channel, message.params[2].asUser(), message.topicSetTime))
27
                     message.metadata, message.channel, message.params[2].asUser(), message.topicSetTime))
25
-            "TOPIC" -> message.sourceUser?.let { yield(ChannelTopicChanged(message.metadata, it, String(message.params[0]), String(message.params[1]))) }
28
+            "TOPIC" -> message.sourceUser?.let {
29
+                if (message.params.size >= 2) {
30
+                    yield(ChannelTopicChanged(message.metadata, it, String(message.params[0]), String(message.params[1])))
31
+                } else {
32
+                    log.warning { "Discarding TOPIC line with insufficient parameters: $message" }
33
+                }
34
+            }
26
         }
35
         }
27
     }.toList()
36
     }.toList()
28
 
37
 

+ 5
- 1
src/main/kotlin/com/dmdirc/ktirc/messages/processors/WelcomeProcessor.kt View File

8
 
8
 
9
     override val commands = arrayOf(RPL_WELCOME)
9
     override val commands = arrayOf(RPL_WELCOME)
10
 
10
 
11
-    override fun process(message: IrcMessage) = listOf(ServerWelcome(message.metadata, message.serverName(), String(message.params[0])))
11
+    override fun process(message: IrcMessage) = listOf(ServerWelcome(
12
+            message.metadata,
13
+            message.serverName(),
14
+            message.localNick()))
12
 
15
 
13
     private fun IrcMessage.serverName() = prefix?.let { String(it) } ?: ""
16
     private fun IrcMessage.serverName() = prefix?.let { String(it) } ?: ""
17
+    private fun IrcMessage.localNick() = if (params.isEmpty()) "" else String(params[0])
14
 
18
 
15
 }
19
 }

+ 7
- 5
src/main/kotlin/com/dmdirc/ktirc/model/IrcMessage.kt View File

23
             label = tags[MessageTag.Label])
23
             label = tags[MessageTag.Label])
24
 
24
 
25
     /** The user that generated the message, if any. */
25
     /** The user that generated the message, if any. */
26
-    val sourceUser by lazy {
27
-        prefix?.asUser()?.apply {
28
-            tags[MessageTag.AccountName]?.let { account = it }
29
-        }
26
+    val sourceUser = prefix?.asUser()?.apply {
27
+        tags[MessageTag.AccountName]?.let { account = it }
30
     }
28
     }
31
 
29
 
32
     private fun String.toLocalDateOrNull() = try {
30
     private fun String.toLocalDateOrNull() = try {
33
         LocalDateTime.ofInstant(Instant.parse(this), currentTimeZoneProvider())
31
         LocalDateTime.ofInstant(Instant.parse(this), currentTimeZoneProvider())
34
-    } catch(e: Exception) {
32
+    } catch (e: Exception) {
35
         log.log(Level.WARNING, e) { "Received unparsable server-time tag: $this" }
33
         log.log(Level.WARNING, e) { "Received unparsable server-time tag: $this" }
36
         null
34
         null
37
     }
35
     }
38
 
36
 
37
+    override fun toString(): String {
38
+        return "IrcMessage(tags=$tags, prefix=${prefix?.let { String(prefix) }}, command='$command', params=${params.map { String(it) }}, metadata=$metadata, sourceUser=$sourceUser)"
39
+    }
40
+
39
 }
41
 }
40
 
42
 
41
 /**
43
 /**

+ 0
- 1
src/main/kotlin/com/dmdirc/ktirc/model/Maps.kt View File

14
     operator fun get(name: String) = synchronized(values) { values.find { caseMappingProvider().areEquivalent(nameOf(it), name) } }
14
     operator fun get(name: String) = synchronized(values) { values.find { caseMappingProvider().areEquivalent(nameOf(it), name) } }
15
 
15
 
16
     internal operator fun plusAssign(value: T): Unit = synchronized(values) {
16
     internal operator fun plusAssign(value: T): Unit = synchronized(values) {
17
-        require(get(nameOf(value)) == null) { "Value already registered: ${nameOf(value)}" }
18
         values.add(value)
17
         values.add(value)
19
     }
18
     }
20
 
19
 

+ 11
- 0
src/test/kotlin/com/dmdirc/ktirc/events/handlers/ChannelStateHandlerTest.kt View File

47
         assertTrue("zerocool" in fakeChannelState["#thegibson"]?.users!!)
47
         assertTrue("zerocool" in fakeChannelState["#thegibson"]?.users!!)
48
     }
48
     }
49
 
49
 
50
+    @Test
51
+    fun `ignores duplicate joiners`() {
52
+        fakeChannelState += ChannelState("#thegibson") { CaseMapping.Rfc }
53
+
54
+        handler.processEvent(ircClient, ChannelJoined(EventMetadata(TestConstants.time), User("zerocool", "dade", "root.localhost"), "#thegibson"))
55
+        handler.processEvent(ircClient, ChannelJoined(EventMetadata(TestConstants.time), User("zerocool", "dade", "root.localhost"), "#thegibson"))
56
+
57
+        assertTrue("zerocool" in fakeChannelState["#thegibson"]?.users!!)
58
+        assertEquals(1, fakeChannelState["#thegibson"]?.users?.count())
59
+    }
60
+
50
     @Test
61
     @Test
51
     fun `clears existing users when getting a new list`() {
62
     fun `clears existing users when getting a new list`() {
52
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }
63
         val channel = ChannelState("#thegibson") { CaseMapping.Rfc }

+ 15
- 8
src/test/kotlin/com/dmdirc/ktirc/messages/processors/CapabilityProcessorTest.kt View File

14
     private val processor = CapabilityProcessor()
14
     private val processor = CapabilityProcessor()
15
 
15
 
16
     @Test
16
     @Test
17
-    fun `CapabilityProcessor does nothing for unknown subcommand`() {
17
+    fun `does nothing for unknown subcommand`() {
18
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "FOO")))
18
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "FOO")))
19
 
19
 
20
         assertTrue(events.isEmpty())
20
         assertTrue(events.isEmpty())
21
     }
21
     }
22
 
22
 
23
     @Test
23
     @Test
24
-    fun `CapabilityProcessor raises ServerCapabilitiesReceived event given no capabilities`() {
24
+    fun `does nothing for missing subcommand`() {
25
+        val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*")))
26
+
27
+        assertTrue(events.isEmpty())
28
+    }
29
+
30
+    @Test
31
+    fun `raises ServerCapabilitiesReceived event given no capabilities`() {
25
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "LS", "")))
32
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "LS", "")))
26
 
33
 
27
         val receivedEvent = events.filterIsInstance<ServerCapabilitiesReceived>()[0]
34
         val receivedEvent = events.filterIsInstance<ServerCapabilitiesReceived>()[0]
30
 
37
 
31
 
38
 
32
     @Test
39
     @Test
33
-    fun `CapabilityProcessor raises ServerCapabilitiesReceived event with known capabilities`() {
40
+    fun `raises ServerCapabilitiesReceived event with known capabilities`() {
34
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "LS", "chghost extended-join invalid")))
41
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "LS", "chghost extended-join invalid")))
35
 
42
 
36
         val receivedEvent = events.filterIsInstance<ServerCapabilitiesReceived>()[0]
43
         val receivedEvent = events.filterIsInstance<ServerCapabilitiesReceived>()[0]
40
     }
47
     }
41
 
48
 
42
     @Test
49
     @Test
43
-    fun `CapabilityProcessor raises ServerCapabilitiesReceived event with values for capabilities`() {
50
+    fun `raises ServerCapabilitiesReceived event with values for capabilities`() {
44
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "LS", "chghost=test123 extended-join=abc=def invalid")))
51
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "LS", "chghost=test123 extended-join=abc=def invalid")))
45
 
52
 
46
         val receivedEvent = events.filterIsInstance<ServerCapabilitiesReceived>()[0]
53
         val receivedEvent = events.filterIsInstance<ServerCapabilitiesReceived>()[0]
50
     }
57
     }
51
 
58
 
52
     @Test
59
     @Test
53
-    fun `CapabilityProcessor overwrites earlier values with later ones for identical capabilities`() {
60
+    fun `overwrites earlier values with later ones for identical capabilities`() {
54
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "LS", "chghost=test123 chghost chghost=456")))
61
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "LS", "chghost=test123 chghost chghost=456")))
55
 
62
 
56
         val receivedEvent = events.filterIsInstance<ServerCapabilitiesReceived>()[0]
63
         val receivedEvent = events.filterIsInstance<ServerCapabilitiesReceived>()[0]
60
 
67
 
61
 
68
 
62
     @Test
69
     @Test
63
-    fun `CapabilityProcessor raises ServerCapabilitiesReceived event with known capabilities for multi-line responses`() {
70
+    fun `raises ServerCapabilitiesReceived event with known capabilities for multi-line responses`() {
64
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "LS", "*", "chghost extended-join invalid")))
71
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "LS", "*", "chghost extended-join invalid")))
65
 
72
 
66
         val receivedEvent = events.filterIsInstance<ServerCapabilitiesReceived>()[0]
73
         val receivedEvent = events.filterIsInstance<ServerCapabilitiesReceived>()[0]
70
     }
77
     }
71
 
78
 
72
     @Test
79
     @Test
73
-    fun `CapabilityProcessor raises ServerCapabilitiesFinished event for final LS responses`() {
80
+    fun `raises ServerCapabilitiesFinished event for final LS responses`() {
74
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "LS", "chghost extended-join invalid")))
81
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "LS", "chghost extended-join invalid")))
75
 
82
 
76
         assertEquals(2, events.size)
83
         assertEquals(2, events.size)
78
     }
85
     }
79
 
86
 
80
     @Test
87
     @Test
81
-    fun `CapabilityProcessor raises ServerCapabilitiesAcknowledged event`() {
88
+    fun `raises ServerCapabilitiesAcknowledged event`() {
82
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "ACK", "chghost=test123 extended-join=abc=def invalid")))
89
         val events = processor.process(IrcMessage(emptyMap(), "the.gibson".toByteArray(), "CAP", params("*", "ACK", "chghost=test123 extended-join=abc=def invalid")))
83
 
90
 
84
         val receivedEvent = events.filterIsInstance<ServerCapabilitiesAcknowledged>()[0]
91
         val receivedEvent = events.filterIsInstance<ServerCapabilitiesAcknowledged>()[0]

+ 7
- 0
src/test/kotlin/com/dmdirc/ktirc/messages/processors/ModeProcessorTest.kt View File

127
         assertFalse(events[0].discovered)
127
         assertFalse(events[0].discovered)
128
     }
128
     }
129
 
129
 
130
+    @Test
131
+    fun `ignores mode changes with a missing target`() {
132
+        val events = ModeProcessor().process(
133
+                IrcMessage(emptyMap(), "acidburn!libby@root.localhost".toByteArray(), "MODE", params("+hax")))
134
+        assertEquals(0, events.size)
135
+    }
136
+
130
 }
137
 }

+ 7
- 0
src/test/kotlin/com/dmdirc/ktirc/messages/processors/NoticeProcessorTest.kt View File

92
                 IrcMessage(emptyMap(), null, "NOTICE", params("#crashandburn", "\u0001BORK\u0001")))
92
                 IrcMessage(emptyMap(), null, "NOTICE", params("#crashandburn", "\u0001BORK\u0001")))
93
         assertEquals(0, events.size)
93
         assertEquals(0, events.size)
94
     }
94
     }
95
+
96
+    @Test
97
+    fun `does nothing if target missing for CTCP replies`() {
98
+        val events = NoticeProcessor().process(
99
+                IrcMessage(emptyMap(), "acidburn!libby@root.localhost".toByteArray(), "NOTICE", params("\u0001BORK\u0001")))
100
+        assertEquals(0, events.size)
101
+    }
95
 }
102
 }

+ 7
- 0
src/test/kotlin/com/dmdirc/ktirc/messages/processors/PrivmsgProcessorTest.kt View File

119
                 IrcMessage(emptyMap(), null, "PRIVMSG", params("#crashandburn", "hack the planet!")))
119
                 IrcMessage(emptyMap(), null, "PRIVMSG", params("#crashandburn", "hack the planet!")))
120
         assertEquals(0, events.size)
120
         assertEquals(0, events.size)
121
     }
121
     }
122
+
123
+    @Test
124
+    fun `does nothing if arguments missing`() {
125
+        val events = PrivmsgProcessor().process(
126
+                IrcMessage(emptyMap(), "acidburn!libby@root.localhost".toByteArray(), "PRIVMSG", params("hack the planet!")))
127
+        assertEquals(0, events.size)
128
+    }
122
 }
129
 }

+ 6
- 0
src/test/kotlin/com/dmdirc/ktirc/messages/processors/TopicProcessorTest.kt View File

75
         assertEquals(0, events.size)
75
         assertEquals(0, events.size)
76
     }
76
     }
77
 
77
 
78
+    @Test
79
+    fun `does nothing when topic is changed with no channel`() {
80
+        val events = processor.process(IrcMessage(emptyMap(), "acidBurn!acidB@the.gibson".toByteArray(), "TOPIC", params("Hack the planet!")))
81
+        assertEquals(0, events.size)
82
+    }
83
+
78
     private fun unixtime() = TestConstants.otherTime.toEpochSecond(currentTimeZoneProvider().rules.getOffset(TestConstants.time)).toString()
84
     private fun unixtime() = TestConstants.otherTime.toEpochSecond(currentTimeZoneProvider().rules.getOffset(TestConstants.time)).toString()
79
 
85
 
80
 }
86
 }

+ 20
- 1
src/test/kotlin/com/dmdirc/ktirc/messages/processors/WelcomeProcessorTest.kt View File

18
     }
18
     }
19
 
19
 
20
     @Test
20
     @Test
21
-    fun `WelcomeProcessor returns server welcome event`() {
21
+    fun `returns server welcome event`() {
22
         val events = processor.process(IrcMessage(emptyMap(), "thegibson.com".toByteArray(), "001", params(
22
         val events = processor.process(IrcMessage(emptyMap(), "thegibson.com".toByteArray(), "001", params(
23
                 "acidBurn", "Welcome to the Internet Relay Network, acidBurn!burn@hacktheplanet.com")))
23
                 "acidBurn", "Welcome to the Internet Relay Network, acidBurn!burn@hacktheplanet.com")))
24
         assertEquals(1, events.size)
24
         assertEquals(1, events.size)
27
         assertEquals("thegibson.com", events[0].server)
27
         assertEquals("thegibson.com", events[0].server)
28
     }
28
     }
29
 
29
 
30
+    @Test
31
+    fun `returns blank server if no prefix is specified`() {
32
+        val events = processor.process(IrcMessage(emptyMap(), null, "001", params(
33
+                "acidBurn", "Welcome to the Internet Relay Network, acidBurn!burn@hacktheplanet.com")))
34
+        assertEquals(1, events.size)
35
+        assertEquals(TestConstants.time, events[0].metadata.time)
36
+        assertEquals("acidBurn", events[0].localNick)
37
+        assertEquals("", events[0].server)
38
+    }
39
+
40
+    @Test
41
+    fun `returns blank local nickname if no parameters provided`() {
42
+        val events = processor.process(IrcMessage(emptyMap(), "thegibson.com".toByteArray(), "001", params()))
43
+        assertEquals(1, events.size)
44
+        assertEquals(TestConstants.time, events[0].metadata.time)
45
+        assertEquals("", events[0].localNick)
46
+        assertEquals("thegibson.com", events[0].server)
47
+    }
48
+
30
 }
49
 }

+ 6
- 7
src/test/kotlin/com/dmdirc/ktirc/model/MapsTest.kt View File

3
 import com.dmdirc.ktirc.io.CaseMapping
3
 import com.dmdirc.ktirc.io.CaseMapping
4
 import org.junit.jupiter.api.Assertions.*
4
 import org.junit.jupiter.api.Assertions.*
5
 import org.junit.jupiter.api.Test
5
 import org.junit.jupiter.api.Test
6
-import org.junit.jupiter.api.assertThrows
7
 
6
 
8
 internal class CaseInsensitiveMapTest {
7
 internal class CaseInsensitiveMapTest {
9
 
8
 
13
     private val map = object : CaseInsensitiveMap<Wrapper>({ caseMapping }, { it -> it.name }) {}
12
     private val map = object : CaseInsensitiveMap<Wrapper>({ caseMapping }, { it -> it.name }) {}
14
 
13
 
15
     @Test
14
     @Test
16
-    fun `CaseInsensitiveMap stores values`() {
15
+    fun `stores values`() {
17
         val value = Wrapper("acidBurn")
16
         val value = Wrapper("acidBurn")
18
 
17
 
19
         map += value
18
         map += value
22
     }
21
     }
23
 
22
 
24
     @Test
23
     @Test
25
-    fun `CaseInsensitiveMap disallows the same value twice`() {
24
+    fun `ignores the same value twice`() {
26
         val value = Wrapper("acidBurn")
25
         val value = Wrapper("acidBurn")
27
 
26
 
27
+        map += value
28
         map += value
28
         map += value
29
 
29
 
30
-        assertThrows<IllegalArgumentException> {
31
-            map += value
32
-        }
30
+        assertSame(value, map["acidBurn"])
31
+        assertEquals(1, map.count())
33
     }
32
     }
34
 
33
 
35
     @Test
34
     @Test
36
-    fun `CaseInsensitiveMap retrieves values using differently cased keys`() {
35
+    fun `retrieves values using differently cased keys`() {
37
         val value = Wrapper("[acidBurn]")
36
         val value = Wrapper("[acidBurn]")
38
         map += value
37
         map += value
39
 
38
 

Loading…
Cancel
Save