Browse Source

Construct IrcClient with a DSL.

This allows more options to be added nicely in the future,
and hides the implementation details from library users.
tags/v0.7.0
Chris Smith 5 years ago
parent
commit
d76c60a47c

+ 3
- 0
CHANGELOG View File

1
 vNEXT (in development)
1
 vNEXT (in development)
2
 
2
 
3
  * Fixed experimental API warnings when using IrcClient
3
  * Fixed experimental API warnings when using IrcClient
4
+ * IrcClients are now constructed using a DSL
5
+   * Users of the library no longer need to care about the implementing class
6
+   * Facilitates adding more options in the future without breaking existing implementations
4
  * (Internal) Minor version updates for Gradle, Kotlin and JUnit
7
  * (Internal) Minor version updates for Gradle, Kotlin and JUnit
5
 
8
 
6
 v0.6.0
9
 v0.6.0

+ 49
- 14
README.md View File

8
 KtIrc is a Kotlin JVM library for connecting to and interacting with IRC servers.
8
 KtIrc is a Kotlin JVM library for connecting to and interacting with IRC servers.
9
 It is still in an early stage of development.
9
 It is still in an early stage of development.
10
 
10
 
11
+## Features
12
+
13
+#### Built for Kotlin
14
+
15
+KtIrc is written in and designed for use in Kotlin; it uses extension methods,
16
+DSLs, sealed classes, and so on, to make it much easier to use than an
17
+equivalent Java library.
18
+
19
+#### Coroutine-powered
20
+
21
+KtIrc uses co-routines for all of its input/output which lets it deal with
22
+IRC messages in the background while your app does other things, without
23
+the overhead of creating a new thread per IRC client.
24
+
25
+#### Modern IRC standards
26
+
27
+KtIrc supports many IRCv3 features such as SASL authentication, message IDs,
28
+server timestamps, replies, reactions, account tags, and more. These features
29
+(where server support is available) make it easier to develop bots and
30
+clients, and enhance IRC with new user-facing functionality.
31
+
11
 ## Setup
32
 ## Setup
12
 
33
 
13
 KtIrc is published to JCenter, so adding it to a gradle build is as simple as:
34
 KtIrc is published to JCenter, so adding it to a gradle build is as simple as:
24
 
45
 
25
 ## Usage
46
 ## Usage
26
 
47
 
27
-The main interface for interacting with KtIrc is the `IrcClientImpl` class. A
28
-simple bot might look like:
48
+Clients are created using a DSL and the `IrcClient` function. At a minimum
49
+you must specify a server and a profile. A simple bot might look like:
29
 
50
 
30
 ```kotlin
51
 ```kotlin
31
-with(IrcClientImpl(Server("my.server.com", 6667), Profile("nick", "realName", "userName"))) {
32
-    onEvent { event ->
33
-        when (event) {
34
-            is ServerReady -> sendJoin("#ktirc")
35
-            is MessageReceived ->
36
-                if (event.message == "!test")
37
-                    reply(event, "Test successful!")
38
-        }
52
+val client = IrcClient {
53
+    server {
54
+        host = "my.server.com"
55
+    } 
56
+    profile {
57
+        nickname = "nick"
58
+        username = "username"
59
+        realName = "Hi there"
39
     }
60
     }
40
-    connect()
41
 }
61
 }
62
+
63
+client.onEvent { event ->
64
+    when (event) {
65
+        is ServerReady ->
66
+            client.sendJoin("#ktirc")
67
+        is ServerDisconnected ->
68
+            client.connect()
69
+        is MessageReceived ->
70
+            if (event.message == "!test")
71
+                client.reply(event, "Test successful!")
72
+    }
73
+}
74
+
75
+client.connect()
42
 ```
76
 ```
43
 
77
 
44
 ## Known issues / FAQ
78
 ## Known issues / FAQ
47
 
81
 
48
 This happens when the IRC server requests an optional client certificate (for use
82
 This happens when the IRC server requests an optional client certificate (for use
49
 in SASL auth, usually). At present there is no support for client certificates in
83
 in SASL auth, usually). At present there is no support for client certificates in
50
-the networking library used by KtIrc. This is tracked upstream in
51
-[ktor#641](https://github.com/ktorio/ktor/issues/641). There is no workaround
52
-other than using an insecure connection.
84
+the networking library used by KtIrc. This is fixed in the
85
+[upstream library](https://github.com/ktorio/ktor/issues/641) and will be included
86
+as soon as snapshot builds are available. There is no workaround other than using
87
+an insecure connection.
53
 
88
 
54
 ### KtIrc connects over IPv4 even when host has IPv6
89
 ### KtIrc connects over IPv4 even when host has IPv6
55
 
90
 

+ 13
- 4
src/itest/kotlin/com/dmdirc/ktirc/KtIrcIntegrationTest.kt View File

1
 package com.dmdirc.ktirc
1
 package com.dmdirc.ktirc
2
 
2
 
3
 import com.dmdirc.irctest.IrcLibraryTests
3
 import com.dmdirc.irctest.IrcLibraryTests
4
-import com.dmdirc.ktirc.model.Profile
5
-import com.dmdirc.ktirc.model.Server
6
 import kotlinx.coroutines.runBlocking
4
 import kotlinx.coroutines.runBlocking
7
 import org.junit.jupiter.api.TestFactory
5
 import org.junit.jupiter.api.TestFactory
8
 
6
 
11
     @TestFactory
9
     @TestFactory
12
     fun dynamicTests() = IrcLibraryTests().getTests(object : IrcLibraryTests.IrcLibrary {
10
     fun dynamicTests() = IrcLibraryTests().getTests(object : IrcLibraryTests.IrcLibrary {
13
 
11
 
14
-        private lateinit var ircClient : IrcClientImpl
12
+        private lateinit var ircClient : IrcClient
15
 
13
 
16
         override fun connect(nick: String, ident: String, realName: String, password: String?) {
14
         override fun connect(nick: String, ident: String, realName: String, password: String?) {
17
-            ircClient = IrcClientImpl(Server("localhost", 12321, password = password), Profile(nick, ident, realName))
15
+            ircClient = IrcClient {
16
+                server {
17
+                    host = "localhost"
18
+                    port = 12321
19
+                    this.password = password
20
+                }
21
+                profile {
22
+                    nickname = nick
23
+                    username = ident
24
+                    this.realName = realName
25
+                }
26
+            }
18
             ircClient.connect()
27
             ircClient.connect()
19
         }
28
         }
20
 
29
 

+ 119
- 0
src/main/kotlin/com/dmdirc/ktirc/Dsl.kt View File

1
+package com.dmdirc.ktirc
2
+
3
+/**
4
+ * Dsl marker for [IrcClient] dsl.
5
+ */
6
+@DslMarker
7
+annotation class IrcClientDsl
8
+
9
+internal data class IrcClientConfig(val server: ServerConfig, val profile: ProfileConfig, val sasl: SaslConfig?)
10
+
11
+/**
12
+ * Dsl for configuring an IRC Client.
13
+ *
14
+ * [server] and [profile] blocks are required. The full range of configuration options are:
15
+ *
16
+ * ```
17
+ * server {
18
+ *     host = "irc.example.com"     // Required
19
+ *     port = 6667
20
+ *     useTls = true
21
+ *     password = "H4ckTh3Pl4n3t"
22
+ * }
23
+ *
24
+ * profile {
25
+ *     nickname = "MyBot"           // Required
26
+ *     username = "bot
27
+ *     realName = "Botomatic v1.2"
28
+ * }
29
+ *
30
+ * sasl {
31
+ *     username = "botaccount"
32
+ *     password = "s3cur3"
33
+ * }
34
+ * ```
35
+ */
36
+@IrcClientDsl
37
+class IrcClientConfigBuilder {
38
+
39
+    private var server: ServerConfig? = null
40
+    private var profile: ProfileConfig? = null
41
+    private var sasl: SaslConfig? = null
42
+
43
+    /**
44
+     * Configures the server that the IrcClient will connect to.
45
+     *
46
+     * At a minimum, [ServerConfig.host] must be supplied.
47
+     */
48
+    @IrcClientDsl
49
+    fun server(block: ServerConfig.() -> Unit) {
50
+        check(server == null) { "server may only be specified once" }
51
+        server = ServerConfig().apply(block).also { check(it.host.isNotEmpty()) { "server.host must be specified" } }
52
+    }
53
+
54
+    /**
55
+     * Configures the profile of the IrcClient user.
56
+     *
57
+     * At a minimum, [ProfileConfig.nickName] must be supplied.
58
+     */
59
+    @IrcClientDsl
60
+    fun profile(block: ProfileConfig.() -> Unit) {
61
+        check(profile == null) { "profile may only be specified once" }
62
+        profile = ProfileConfig().apply(block).also { check(it.nickname.isNotEmpty()) { "profile.nickname must be specified" } }
63
+    }
64
+
65
+    /**
66
+     * Configures SASL authentication (optional).
67
+     */
68
+    @IrcClientDsl
69
+    fun sasl(block: SaslConfig.() -> Unit) {
70
+        check(sasl == null) { "sasl may only be specified once" }
71
+        sasl = SaslConfig().apply(block)
72
+    }
73
+
74
+    internal fun build() =
75
+            IrcClientConfig(
76
+                    checkNotNull(server) { "Server must be specified " },
77
+                    checkNotNull(profile) { "Profile must be specified" },
78
+                    sasl)
79
+
80
+}
81
+
82
+/**
83
+ * Dsl for configuring a server.
84
+ */
85
+@IrcClientDsl
86
+class ServerConfig {
87
+    /** The hostname (or IP address) of the server to connect to. */
88
+    var host: String = ""
89
+    /** The port to connect on. Defaults to 6667. */
90
+    var port: Int = 6667
91
+    /** Whether or not to use TLS (an encrypted connection). */
92
+    var useTls: Boolean = false
93
+    /** The password required to connect to the server, if any. */
94
+    var password: String? = null
95
+}
96
+
97
+/**
98
+ * Dsl for configuring a profile.
99
+ */
100
+@IrcClientDsl
101
+class ProfileConfig {
102
+    /** The initial nickname to use when connecting. */
103
+    var nickname: String = ""
104
+    /** The username (used in place of an ident response) to provide to the server. */
105
+    var username: String = "KtIrc"
106
+    /** The "real name" to provide to the server. */
107
+    var realName: String = "KtIrc User"
108
+}
109
+
110
+/**
111
+ * Dsl for configuring SASL authentication.
112
+ */
113
+@IrcClientDsl
114
+class SaslConfig {
115
+    /** The username to provide when authenticating using SASL. */
116
+    var username: String = ""
117
+    /** The username to provide when authenticating using SASL. */
118
+    var password: String = ""
119
+}

+ 29
- 12
src/main/kotlin/com/dmdirc/ktirc/IrcClient.kt View File

4
 import com.dmdirc.ktirc.io.*
4
 import com.dmdirc.ktirc.io.*
5
 import com.dmdirc.ktirc.messages.*
5
 import com.dmdirc.ktirc.messages.*
6
 import com.dmdirc.ktirc.model.*
6
 import com.dmdirc.ktirc.model.*
7
+import com.dmdirc.ktirc.sasl.PlainMechanism
8
+import com.dmdirc.ktirc.sasl.SaslMechanism
7
 import com.dmdirc.ktirc.util.currentTimeProvider
9
 import com.dmdirc.ktirc.util.currentTimeProvider
8
 import com.dmdirc.ktirc.util.logger
10
 import com.dmdirc.ktirc.util.logger
9
 import io.ktor.util.KtorExperimentalAPI
11
 import io.ktor.util.KtorExperimentalAPI
19
     val serverState: ServerState
21
     val serverState: ServerState
20
     val channelState: ChannelStateMap
22
     val channelState: ChannelStateMap
21
     val userState: UserState
23
     val userState: UserState
22
-    val profile: Profile
24
+    val hasSaslConfig: Boolean
23
 
25
 
24
     val caseMapping: CaseMapping
26
     val caseMapping: CaseMapping
25
         get() = serverState.features[ServerFeature.ServerCaseMapping] ?: CaseMapping.Rfc
27
         get() = serverState.features[ServerFeature.ServerCaseMapping] ?: CaseMapping.Rfc
79
 }
81
 }
80
 
82
 
81
 /**
83
 /**
82
- * Concrete implementation of an [IrcClient].
84
+ * Constructs a new [IrcClient] using a configuration DSL.
83
  *
85
  *
84
- * @param server The server to connect to.
85
- * @param profile The user details to use when connecting.
86
+ * See [IrcClientConfigBuilder] for details of all options
87
+ */
88
+@IrcClientDsl
89
+@Suppress("FunctionName")
90
+fun IrcClient(block: IrcClientConfigBuilder.() -> Unit): IrcClient =
91
+        IrcClientImpl(IrcClientConfigBuilder().apply(block).build())
92
+
93
+/**
94
+ * Concrete implementation of an [IrcClient].
86
  */
95
  */
87
 // TODO: How should alternative nicknames work?
96
 // TODO: How should alternative nicknames work?
88
 // TODO: Should IRC Client take a pool of servers and rotate through, or make the caller do that?
97
 // TODO: Should IRC Client take a pool of servers and rotate through, or make the caller do that?
89
 // TODO: Should there be a default profile?
98
 // TODO: Should there be a default profile?
90
-class IrcClientImpl(private val server: Server, override val profile: Profile) : IrcClient, CoroutineScope {
99
+internal class IrcClientImpl(private val config: IrcClientConfig) : IrcClient, CoroutineScope {
91
 
100
 
92
     private val log by logger()
101
     private val log by logger()
93
 
102
 
98
     @KtorExperimentalAPI
107
     @KtorExperimentalAPI
99
     internal var socketFactory: (CoroutineScope, String, Int, Boolean) -> LineBufferedSocket = ::KtorLineBufferedSocket
108
     internal var socketFactory: (CoroutineScope, String, Int, Boolean) -> LineBufferedSocket = ::KtorLineBufferedSocket
100
 
109
 
101
-    override val serverState = ServerState(profile.initialNick, server.host)
110
+    override val serverState = ServerState(config.profile.nickname, config.server.host, getSaslMechanisms())
102
     override val channelState = ChannelStateMap { caseMapping }
111
     override val channelState = ChannelStateMap { caseMapping }
103
     override val userState = UserState { caseMapping }
112
     override val userState = UserState { caseMapping }
113
+    override val hasSaslConfig = config.sasl != null
104
 
114
 
105
     private val messageHandler = MessageHandler(messageProcessors.toList(), eventHandlers.toMutableList())
115
     private val messageHandler = MessageHandler(messageProcessors.toList(), eventHandlers.toMutableList())
106
 
116
 
110
     private val connecting = AtomicBoolean(false)
120
     private val connecting = AtomicBoolean(false)
111
 
121
 
112
     override fun send(message: String) {
122
     override fun send(message: String) {
113
-        socket?.sendChannel?.offer(message.toByteArray()) ?: log.warning { "No send channel for message: $message"}
123
+        socket?.sendChannel?.offer(message.toByteArray()) ?: log.warning { "No send channel for message: $message" }
114
     }
124
     }
115
 
125
 
116
     override fun connect() {
126
     override fun connect() {
117
         check(!connecting.getAndSet(true))
127
         check(!connecting.getAndSet(true))
118
 
128
 
119
         @Suppress("EXPERIMENTAL_API_USAGE")
129
         @Suppress("EXPERIMENTAL_API_USAGE")
120
-        with(socketFactory(this, server.host, server.port, server.tls)) {
130
+        with(socketFactory(this, config.server.host, config.server.port, config.server.useTls)) {
121
             // TODO: Proper error handling - what if connect() fails?
131
             // TODO: Proper error handling - what if connect() fails?
122
             socket = this
132
             socket = this
123
 
133
 
128
                 emitEvent(ServerConnected(currentTimeProvider()))
138
                 emitEvent(ServerConnected(currentTimeProvider()))
129
                 sendCapabilityList()
139
                 sendCapabilityList()
130
                 sendPasswordIfPresent()
140
                 sendPasswordIfPresent()
131
-                sendNickChange(profile.initialNick)
132
-                // TODO: Send correct host
133
-                sendUser(profile.userName, profile.realName)
141
+                sendNickChange(config.profile.nickname)
142
+                sendUser(config.profile.username, config.profile.realName)
134
                 messageHandler.processMessages(this@IrcClientImpl, receiveChannel.map { parser.parse(it) })
143
                 messageHandler.processMessages(this@IrcClientImpl, receiveChannel.map { parser.parse(it) })
135
                 reset()
144
                 reset()
136
                 emitEvent(ServerDisconnected(currentTimeProvider()))
145
                 emitEvent(ServerDisconnected(currentTimeProvider()))
152
     }
161
     }
153
 
162
 
154
     private fun emitEvent(event: IrcEvent) = messageHandler.emitEvent(this, event)
163
     private fun emitEvent(event: IrcEvent) = messageHandler.emitEvent(this, event)
155
-    private fun sendPasswordIfPresent() = server.password?.let(this::sendPassword)
164
+    private fun sendPasswordIfPresent() = config.server.password?.let(this::sendPassword)
156
 
165
 
157
     internal fun reset() {
166
     internal fun reset() {
158
         serverState.reset()
167
         serverState.reset()
162
         connecting.set(false)
171
         connecting.set(false)
163
     }
172
     }
164
 
173
 
174
+    private fun getSaslMechanisms(): Collection<SaslMechanism> {
175
+        // TODO: Move this somewhere else
176
+        // TODO: Allow mechanisms to be configured
177
+        config.sasl?.let {
178
+            return listOf(PlainMechanism(it))
179
+        } ?: return emptyList()
180
+    }
181
+
165
 }
182
 }

+ 1
- 4
src/main/kotlin/com/dmdirc/ktirc/events/CapabilitiesHandler.kt View File

52
             log.info { "Acknowledged capabilities: ${capabilities.keys.map { it.name }.toList()}" }
52
             log.info { "Acknowledged capabilities: ${capabilities.keys.map { it.name }.toList()}" }
53
             enabledCapabilities.putAll(capabilities)
53
             enabledCapabilities.putAll(capabilities)
54
 
54
 
55
-            if (client.hasCredentials) {
55
+            if (client.hasSaslConfig) {
56
                 client.serverState.sasl.getPreferredSaslMechanism(enabledCapabilities[Capability.SaslAuthentication])?.let { mechanism ->
56
                 client.serverState.sasl.getPreferredSaslMechanism(enabledCapabilities[Capability.SaslAuthentication])?.let { mechanism ->
57
                     log.info { "Attempting SASL authentication using ${mechanism.ircName}" }
57
                     log.info { "Attempting SASL authentication using ${mechanism.ircName}" }
58
                     client.serverState.sasl.currentMechanism = mechanism
58
                     client.serverState.sasl.currentMechanism = mechanism
100
         return if (data.isEmpty()) null else data
100
         return if (data.isEmpty()) null else data
101
     }
101
     }
102
 
102
 
103
-    private val IrcClient.hasCredentials
104
-            get() = profile.authUsername != null && profile.authPassword != null
105
-
106
 }
103
 }

+ 0
- 18
src/main/kotlin/com/dmdirc/ktirc/model/Profile.kt View File

1
-package com.dmdirc.ktirc.model
2
-
3
-/**
4
- * Describes the client's profile information that will be provided to a server.
5
- *
6
- * @param initialNick The initial nickname to attempt to use
7
- * @param realName The real name to provide to the IRC server
8
- * @param userName The username to use if your system doesn't supply an IDENT response (or the server doesn't ask)
9
- * @param authUsername The username to authenticate over SASL with (e.g. services account)
10
- * @param authPassword The password to authenticate the [authUsername] account with
11
- */
12
-data class Profile(
13
-        val initialNick: String,
14
-        val realName: String,
15
-        val userName: String,
16
-        val authUsername: String? = null,
17
-        val authPassword: String? = null
18
-)

+ 0
- 6
src/main/kotlin/com/dmdirc/ktirc/model/Server.kt View File

1
-package com.dmdirc.ktirc.model
2
-
3
-/**
4
- * Describes a server to connect to.
5
- */
6
-data class Server(val host: String, val port: Int, val tls: Boolean = false, val password: String? = null)

+ 3
- 4
src/main/kotlin/com/dmdirc/ktirc/model/ServerState.kt View File

2
 
2
 
3
 import com.dmdirc.ktirc.io.CaseMapping
3
 import com.dmdirc.ktirc.io.CaseMapping
4
 import com.dmdirc.ktirc.sasl.SaslMechanism
4
 import com.dmdirc.ktirc.sasl.SaslMechanism
5
-import com.dmdirc.ktirc.sasl.supportedSaslMechanisms
6
 import com.dmdirc.ktirc.util.logger
5
 import com.dmdirc.ktirc.util.logger
7
 import kotlin.reflect.KClass
6
 import kotlin.reflect.KClass
8
 
7
 
12
 class ServerState internal constructor(
11
 class ServerState internal constructor(
13
         private val initialNickname: String,
12
         private val initialNickname: String,
14
         private val initialServerName: String,
13
         private val initialServerName: String,
15
-        saslMechanisms: Collection<SaslMechanism> = supportedSaslMechanisms) {
14
+        saslMechanisms: Collection<SaslMechanism>) {
16
 
15
 
17
     private val log by logger()
16
     private val log by logger()
18
 
17
 
26
     /**
25
     /**
27
      * What we believe our current nickname to be on the server.
26
      * What we believe our current nickname to be on the server.
28
      *
27
      *
29
-     * Initially this will be the nickname provided in the [Profile]. It will be updated to the actual nickname
28
+     * Initially this will be the nickname provided in the config. It will be updated to the actual nickname
30
      * in use when connecting. Once you have received a [com.dmdirc.ktirc.events.ServerWelcome] event you can
29
      * in use when connecting. Once you have received a [com.dmdirc.ktirc.events.ServerWelcome] event you can
31
      * rely on this value being current.
30
      * rely on this value being current.
32
      * */
31
      * */
36
     /**
35
     /**
37
      * The name of the server we are connected to.
36
      * The name of the server we are connected to.
38
      *
37
      *
39
-     * Initially this will be the hostname or IP address provided in the [Server]. It will be updated to the server's
38
+     * Initially this will be the hostname or IP address provided in the config. It will be updated to the server's
40
      * self-reported hostname when connecting. Once you have received a [com.dmdirc.ktirc.events.ServerWelcome] event
39
      * self-reported hostname when connecting. Once you have received a [com.dmdirc.ktirc.events.ServerWelcome] event
41
      * you can rely on this value being current.
40
      * you can rely on this value being current.
42
      */
41
      */

+ 4
- 3
src/main/kotlin/com/dmdirc/ktirc/sasl/Plain.kt View File

1
 package com.dmdirc.ktirc.sasl
1
 package com.dmdirc.ktirc.sasl
2
 
2
 
3
 import com.dmdirc.ktirc.IrcClient
3
 import com.dmdirc.ktirc.IrcClient
4
+import com.dmdirc.ktirc.SaslConfig
4
 import com.dmdirc.ktirc.messages.sendAuthenticationMessage
5
 import com.dmdirc.ktirc.messages.sendAuthenticationMessage
5
 
6
 
6
-internal class PlainMechanism : SaslMechanism {
7
+internal class PlainMechanism(private val saslConfig: SaslConfig) : SaslMechanism {
7
 
8
 
8
     override val ircName = "PLAIN"
9
     override val ircName = "PLAIN"
9
     override val priority = 0
10
     override val priority = 0
10
 
11
 
11
     override fun handleAuthenticationEvent(client: IrcClient, data: ByteArray?) {
12
     override fun handleAuthenticationEvent(client: IrcClient, data: ByteArray?) {
12
-        with (client.profile) {
13
-            client.sendAuthenticationMessage("$authUsername\u0000$authUsername\u0000$authPassword".toByteArray().toBase64())
13
+        with (saslConfig) {
14
+            client.sendAuthenticationMessage("$username\u0000$username\u0000$password".toByteArray().toBase64())
14
         }
15
         }
15
     }
16
     }
16
 
17
 

+ 0
- 4
src/main/kotlin/com/dmdirc/ktirc/sasl/SaslMechanism.kt View File

10
     fun handleAuthenticationEvent(client: IrcClient, data: ByteArray?)
10
     fun handleAuthenticationEvent(client: IrcClient, data: ByteArray?)
11
 
11
 
12
 }
12
 }
13
-
14
-internal val supportedSaslMechanisms = listOf<SaslMechanism>(
15
-        PlainMechanism()
16
-)

+ 125
- 0
src/test/kotlin/com/dmdirc/ktirc/DslTest.kt View File

1
+package com.dmdirc.ktirc
2
+
3
+import org.junit.jupiter.api.Assertions.assertEquals
4
+import org.junit.jupiter.api.Assertions.assertTrue
5
+import org.junit.jupiter.api.Test
6
+import org.junit.jupiter.api.assertThrows
7
+
8
+internal class IrcClientConfigBuilderTest {
9
+
10
+    @Test
11
+    fun `throws if server is defined twice`() {
12
+        assertThrows<IllegalStateException> {
13
+            IrcClientConfigBuilder().apply {
14
+                server { host = "1" }
15
+                server { host = "2" }
16
+            }
17
+        }
18
+    }
19
+
20
+    @Test
21
+    fun `throws if no host is provided`() {
22
+        assertThrows<IllegalStateException> {
23
+            IrcClientConfigBuilder().apply {
24
+                server {}
25
+            }
26
+        }
27
+    }
28
+
29
+    @Test
30
+    fun `throws if profile is defined twice`() {
31
+        assertThrows<IllegalStateException> {
32
+            IrcClientConfigBuilder().apply {
33
+                profile { nickname = "acidBurn" }
34
+                profile { nickname = "zeroCool" }
35
+            }
36
+        }
37
+    }
38
+
39
+    @Test
40
+    fun `throws if no nickname is provided`() {
41
+        assertThrows<IllegalStateException> {
42
+            IrcClientConfigBuilder().apply {
43
+                profile {}
44
+            }
45
+        }
46
+    }
47
+
48
+    @Test
49
+    fun `throws if sasl is defined twice`() {
50
+        assertThrows<IllegalStateException> {
51
+            IrcClientConfigBuilder().apply {
52
+                sasl {}
53
+                sasl {}
54
+            }
55
+        }
56
+    }
57
+
58
+    @Test
59
+    fun `throws if server is not defined`() {
60
+        assertThrows<IllegalStateException> {
61
+            IrcClientConfigBuilder().apply {
62
+                profile { nickname = "acidBurn" }
63
+            }.build()
64
+        }
65
+    }
66
+
67
+    @Test
68
+    fun `throws if profile is not defined`() {
69
+        assertThrows<IllegalStateException> {
70
+            IrcClientConfigBuilder().apply {
71
+                server { host = "thegibson.com" }
72
+            }.build()
73
+        }
74
+    }
75
+
76
+    @Test
77
+    fun `applies server settings`() {
78
+        val config = IrcClientConfigBuilder().apply {
79
+            profile { nickname = "acidBurn" }
80
+            server {
81
+                host = "thegibson.com"
82
+                port = 1337
83
+                password = "h4cktheplan3t"
84
+                useTls = true
85
+            }
86
+        }.build()
87
+
88
+        assertEquals("thegibson.com", config.server.host)
89
+        assertEquals(1337, config.server.port)
90
+        assertEquals("h4cktheplan3t", config.server.password)
91
+        assertTrue(config.server.useTls)
92
+    }
93
+
94
+    @Test
95
+    fun `applies profile settings`() {
96
+        val config = IrcClientConfigBuilder().apply {
97
+            profile {
98
+                nickname = "acidBurn"
99
+                username = "acidB"
100
+                realName = "Kate"
101
+            }
102
+            server { host = "thegibson.com" }
103
+        }.build()
104
+
105
+        assertEquals("acidBurn", config.profile.nickname)
106
+        assertEquals("acidB", config.profile.username)
107
+        assertEquals("Kate", config.profile.realName)
108
+    }
109
+
110
+    @Test
111
+    fun `applies sasl settings`() {
112
+        val config = IrcClientConfigBuilder().apply {
113
+            profile { nickname = "acidBurn" }
114
+            server { host = "thegibson.com" }
115
+            sasl {
116
+                username = "acidBurn"
117
+                password = "h4ckthepl@net"
118
+            }
119
+        }.build()
120
+
121
+        assertEquals("acidBurn", config.sasl?.username)
122
+        assertEquals("h4ckthepl@net", config.sasl?.password)
123
+    }
124
+
125
+}

+ 39
- 18
src/test/kotlin/com/dmdirc/ktirc/IrcClientTest.kt View File

6
 import com.dmdirc.ktirc.events.ServerWelcome
6
 import com.dmdirc.ktirc.events.ServerWelcome
7
 import com.dmdirc.ktirc.io.CaseMapping
7
 import com.dmdirc.ktirc.io.CaseMapping
8
 import com.dmdirc.ktirc.io.LineBufferedSocket
8
 import com.dmdirc.ktirc.io.LineBufferedSocket
9
-import com.dmdirc.ktirc.model.*
9
+import com.dmdirc.ktirc.model.ChannelState
10
+import com.dmdirc.ktirc.model.ServerFeature
11
+import com.dmdirc.ktirc.model.User
10
 import com.dmdirc.ktirc.util.currentTimeProvider
12
 import com.dmdirc.ktirc.util.currentTimeProvider
11
 import com.nhaarman.mockitokotlin2.*
13
 import com.nhaarman.mockitokotlin2.*
12
 import io.ktor.util.KtorExperimentalAPI
14
 import io.ktor.util.KtorExperimentalAPI
46
 
48
 
47
     private val mockEventHandler = mock<(IrcEvent) -> Unit>()
49
     private val mockEventHandler = mock<(IrcEvent) -> Unit>()
48
 
50
 
51
+    private val profileConfig = ProfileConfig().apply {
52
+        nickname = NICK
53
+        realName = REAL_NAME
54
+        username = USER_NAME
55
+    }
56
+
57
+    private val normalConfig = IrcClientConfig(ServerConfig().apply {
58
+        host = HOST
59
+        port = PORT
60
+    }, profileConfig, null)
61
+
49
     @BeforeEach
62
     @BeforeEach
50
     fun setUp() {
63
     fun setUp() {
51
         currentTimeProvider = { TestConstants.time }
64
         currentTimeProvider = { TestConstants.time }
53
 
66
 
54
     @Test
67
     @Test
55
     fun `uses socket factory to create a new socket on connect`() {
68
     fun `uses socket factory to create a new socket on connect`() {
56
-        val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
69
+        val client = IrcClientImpl(normalConfig)
57
         client.socketFactory = mockSocketFactory
70
         client.socketFactory = mockSocketFactory
58
         client.connect()
71
         client.connect()
59
 
72
 
62
 
75
 
63
     @Test
76
     @Test
64
     fun `uses socket factory to create a new tls on connect`() {
77
     fun `uses socket factory to create a new tls on connect`() {
65
-        val client = IrcClientImpl(Server(HOST, PORT, true), Profile(NICK, REAL_NAME, USER_NAME))
78
+        val client = IrcClientImpl(IrcClientConfig(ServerConfig().apply {
79
+            host = HOST
80
+            port = PORT
81
+            useTls = true
82
+        }, profileConfig, null))
66
         client.socketFactory = mockSocketFactory
83
         client.socketFactory = mockSocketFactory
67
         client.connect()
84
         client.connect()
68
 
85
 
71
 
88
 
72
     @Test
89
     @Test
73
     fun `throws if socket already exists`() {
90
     fun `throws if socket already exists`() {
74
-        val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
91
+        val client = IrcClientImpl(normalConfig)
75
         client.socketFactory = mockSocketFactory
92
         client.socketFactory = mockSocketFactory
76
         client.connect()
93
         client.connect()
77
 
94
 
83
     @Test
100
     @Test
84
     fun `emits connection events with local time`() = runBlocking {
101
     fun `emits connection events with local time`() = runBlocking {
85
         currentTimeProvider = { TestConstants.time }
102
         currentTimeProvider = { TestConstants.time }
86
-        val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
103
+        val client = IrcClientImpl(normalConfig)
87
         client.socketFactory = mockSocketFactory
104
         client.socketFactory = mockSocketFactory
88
         client.onEvent(mockEventHandler)
105
         client.onEvent(mockEventHandler)
89
         client.connect()
106
         client.connect()
100
 
117
 
101
     @Test
118
     @Test
102
     fun `sends basic connection strings`() = runBlocking {
119
     fun `sends basic connection strings`() = runBlocking {
103
-        val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
120
+        val client = IrcClientImpl(normalConfig)
104
         client.socketFactory = mockSocketFactory
121
         client.socketFactory = mockSocketFactory
105
         client.connect()
122
         client.connect()
106
 
123
 
111
 
128
 
112
     @Test
129
     @Test
113
     fun `sends password first, when present`() = runBlocking {
130
     fun `sends password first, when present`() = runBlocking {
114
-        val client = IrcClientImpl(Server(HOST, PORT, password = PASSWORD), Profile(NICK, REAL_NAME, USER_NAME))
131
+        val client = IrcClientImpl(IrcClientConfig(ServerConfig().apply {
132
+            host = HOST
133
+            port = PORT
134
+            password = PASSWORD
135
+        }, profileConfig, null))
115
         client.socketFactory = mockSocketFactory
136
         client.socketFactory = mockSocketFactory
116
         client.connect()
137
         client.connect()
117
 
138
 
121
 
142
 
122
     @Test
143
     @Test
123
     fun `sends events to provided event handler`() {
144
     fun `sends events to provided event handler`() {
124
-        val client = IrcClientImpl(Server(HOST, PORT, password = PASSWORD), Profile(NICK, REAL_NAME, USER_NAME))
145
+        val client = IrcClientImpl(normalConfig)
125
         client.socketFactory = mockSocketFactory
146
         client.socketFactory = mockSocketFactory
126
         client.onEvent(mockEventHandler)
147
         client.onEvent(mockEventHandler)
127
 
148
 
136
 
157
 
137
     @Test
158
     @Test
138
     fun `gets case mapping from server features`() {
159
     fun `gets case mapping from server features`() {
139
-        val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
160
+        val client = IrcClientImpl(normalConfig)
140
         client.serverState.features[ServerFeature.ServerCaseMapping] = CaseMapping.RfcStrict
161
         client.serverState.features[ServerFeature.ServerCaseMapping] = CaseMapping.RfcStrict
141
         assertEquals(CaseMapping.RfcStrict, client.caseMapping)
162
         assertEquals(CaseMapping.RfcStrict, client.caseMapping)
142
     }
163
     }
143
 
164
 
144
     @Test
165
     @Test
145
     fun `indicates if user is local user or not`() {
166
     fun `indicates if user is local user or not`() {
146
-        val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
167
+        val client = IrcClientImpl(normalConfig)
147
         client.serverState.localNickname = "[acidBurn]"
168
         client.serverState.localNickname = "[acidBurn]"
148
 
169
 
149
         assertTrue(client.isLocalUser(User("{acidBurn}", "libby", "root.localhost")))
170
         assertTrue(client.isLocalUser(User("{acidBurn}", "libby", "root.localhost")))
152
 
173
 
153
     @Test
174
     @Test
154
     fun `indicates if nickname is local user or not`() {
175
     fun `indicates if nickname is local user or not`() {
155
-        val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
176
+        val client = IrcClientImpl(normalConfig)
156
         client.serverState.localNickname = "[acidBurn]"
177
         client.serverState.localNickname = "[acidBurn]"
157
 
178
 
158
         assertTrue(client.isLocalUser("{acidBurn}"))
179
         assertTrue(client.isLocalUser("{acidBurn}"))
161
 
182
 
162
     @Test
183
     @Test
163
     fun `uses current case mapping to check local user`() {
184
     fun `uses current case mapping to check local user`() {
164
-        val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
185
+        val client = IrcClientImpl(normalConfig)
165
         client.serverState.localNickname = "[acidBurn]"
186
         client.serverState.localNickname = "[acidBurn]"
166
         client.serverState.features[ServerFeature.ServerCaseMapping] = CaseMapping.Ascii
187
         client.serverState.features[ServerFeature.ServerCaseMapping] = CaseMapping.Ascii
167
         assertFalse(client.isLocalUser(User("{acidBurn}", "libby", "root.localhost")))
188
         assertFalse(client.isLocalUser(User("{acidBurn}", "libby", "root.localhost")))
169
 
190
 
170
     @Test
191
     @Test
171
     fun `sends text to socket`() = runBlocking {
192
     fun `sends text to socket`() = runBlocking {
172
-        val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
193
+        val client = IrcClientImpl(normalConfig)
173
         client.socketFactory = mockSocketFactory
194
         client.socketFactory = mockSocketFactory
174
         client.connect()
195
         client.connect()
175
 
196
 
189
 
210
 
190
     @Test
211
     @Test
191
     fun `disconnects the socket`() = runBlocking {
212
     fun `disconnects the socket`() = runBlocking {
192
-        val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
213
+        val client = IrcClientImpl(normalConfig)
193
         client.socketFactory = mockSocketFactory
214
         client.socketFactory = mockSocketFactory
194
         client.connect()
215
         client.connect()
195
 
216
 
200
 
221
 
201
     @Test
222
     @Test
202
     fun `sends messages in order`() = runBlocking {
223
     fun `sends messages in order`() = runBlocking {
203
-        val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
224
+        val client = IrcClientImpl(normalConfig)
204
         client.socketFactory = mockSocketFactory
225
         client.socketFactory = mockSocketFactory
205
         client.connect()
226
         client.connect()
206
 
227
 
220
 
241
 
221
     @Test
242
     @Test
222
     fun `defaults local nickname to profile`() = runBlocking {
243
     fun `defaults local nickname to profile`() = runBlocking {
223
-        val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
244
+        val client = IrcClientImpl(normalConfig)
224
         assertEquals(NICK, client.serverState.localNickname)
245
         assertEquals(NICK, client.serverState.localNickname)
225
     }
246
     }
226
 
247
 
227
     @Test
248
     @Test
228
     fun `defaults server name to host name`() = runBlocking {
249
     fun `defaults server name to host name`() = runBlocking {
229
-        val client = IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))
250
+        val client = IrcClientImpl(normalConfig)
230
         assertEquals(HOST, client.serverState.serverName)
251
         assertEquals(HOST, client.serverState.serverName)
231
     }
252
     }
232
 
253
 
233
     @Test
254
     @Test
234
     fun `reset clears all state`() {
255
     fun `reset clears all state`() {
235
-        with (IrcClientImpl(Server(HOST, PORT), Profile(NICK, REAL_NAME, USER_NAME))) {
256
+        with(IrcClientImpl(normalConfig)) {
236
             userState += User("acidBurn")
257
             userState += User("acidBurn")
237
             channelState += ChannelState("#thegibson") { CaseMapping.Rfc }
258
             channelState += ChannelState("#thegibson") { CaseMapping.Rfc }
238
             serverState.serverName = "root.$HOST"
259
             serverState.serverName = "root.$HOST"

+ 5
- 9
src/test/kotlin/com/dmdirc/ktirc/events/CapabilitiesHandlerTest.kt View File

4
 import com.dmdirc.ktirc.TestConstants
4
 import com.dmdirc.ktirc.TestConstants
5
 import com.dmdirc.ktirc.model.CapabilitiesNegotiationState
5
 import com.dmdirc.ktirc.model.CapabilitiesNegotiationState
6
 import com.dmdirc.ktirc.model.Capability
6
 import com.dmdirc.ktirc.model.Capability
7
-import com.dmdirc.ktirc.model.Profile
8
 import com.dmdirc.ktirc.model.ServerState
7
 import com.dmdirc.ktirc.model.ServerState
9
 import com.dmdirc.ktirc.sasl.SaslMechanism
8
 import com.dmdirc.ktirc.sasl.SaslMechanism
10
 import com.dmdirc.ktirc.sasl.fromBase64
9
 import com.dmdirc.ktirc.sasl.fromBase64
32
 
31
 
33
     private val handler = CapabilitiesHandler()
32
     private val handler = CapabilitiesHandler()
34
     private val serverState = ServerState("", "", listOf(saslMech1, saslMech2, saslMech3))
33
     private val serverState = ServerState("", "", listOf(saslMech1, saslMech2, saslMech3))
35
-    private val nonSaslProfile = Profile("acidBurn", "Kate Libby", "acidB")
36
-    private val saslProfile = Profile("acidBurn", "Kate Libby", "acidB", "acidB", "HackThePlan3t!")
37
     private val ircClient = mock<IrcClient> {
34
     private val ircClient = mock<IrcClient> {
38
         on { serverState } doReturn serverState
35
         on { serverState } doReturn serverState
39
-        on { profile } doReturn nonSaslProfile
40
     }
36
     }
41
 
37
 
42
     @Test
38
     @Test
96
 
92
 
97
     @Test
93
     @Test
98
     fun `sends END when capabilities acknowledged and no sasl state`() {
94
     fun `sends END when capabilities acknowledged and no sasl state`() {
99
-        whenever(ircClient.profile).thenReturn(saslProfile)
95
+        whenever(ircClient.hasSaslConfig).thenReturn(true)
100
         handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
96
         handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
101
                 Capability.EchoMessages to "",
97
                 Capability.EchoMessages to "",
102
                 Capability.HostsInNamesReply to "123"
98
                 Capability.HostsInNamesReply to "123"
107
 
103
 
108
     @Test
104
     @Test
109
     fun `sends END when capabilities acknowledged and no shared mechanism`() {
105
     fun `sends END when capabilities acknowledged and no shared mechanism`() {
110
-        whenever(ircClient.profile).thenReturn(saslProfile)
106
+        whenever(ircClient.hasSaslConfig).thenReturn(true)
111
         handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
107
         handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
112
                 Capability.SaslAuthentication to "fake1,fake2",
108
                 Capability.SaslAuthentication to "fake1,fake2",
113
                 Capability.HostsInNamesReply to "123"
109
                 Capability.HostsInNamesReply to "123"
118
 
114
 
119
     @Test
115
     @Test
120
     fun `sends AUTHENTICATE when capabilities acknowledged with shared mechanism`() {
116
     fun `sends AUTHENTICATE when capabilities acknowledged with shared mechanism`() {
121
-        whenever(ircClient.profile).thenReturn(saslProfile)
117
+        whenever(ircClient.hasSaslConfig).thenReturn(true)
122
         handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
118
         handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
123
                 Capability.SaslAuthentication to "mech1,fake2",
119
                 Capability.SaslAuthentication to "mech1,fake2",
124
                 Capability.HostsInNamesReply to "123"
120
                 Capability.HostsInNamesReply to "123"
129
 
125
 
130
     @Test
126
     @Test
131
     fun `sets current SASL mechanism when capabilities acknowledged with shared mechanism`() {
127
     fun `sets current SASL mechanism when capabilities acknowledged with shared mechanism`() {
132
-        whenever(ircClient.profile).thenReturn(saslProfile)
128
+        whenever(ircClient.hasSaslConfig).thenReturn(true)
133
         handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
129
         handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
134
                 Capability.SaslAuthentication to "mech1,fake2",
130
                 Capability.SaslAuthentication to "mech1,fake2",
135
                 Capability.HostsInNamesReply to "123"
131
                 Capability.HostsInNamesReply to "123"
140
 
136
 
141
     @Test
137
     @Test
142
     fun `updates negotiation state when capabilities acknowledged with shared mechanism`() {
138
     fun `updates negotiation state when capabilities acknowledged with shared mechanism`() {
143
-        whenever(ircClient.profile).thenReturn(saslProfile)
139
+        whenever(ircClient.hasSaslConfig).thenReturn(true)
144
         handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
140
         handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
145
                 Capability.SaslAuthentication to "mech1,fake2",
141
                 Capability.SaslAuthentication to "mech1,fake2",
146
                 Capability.HostsInNamesReply to "123"
142
                 Capability.HostsInNamesReply to "123"

+ 1
- 1
src/test/kotlin/com/dmdirc/ktirc/events/ChannelStateHandlerTest.kt View File

13
 
13
 
14
     private val handler = ChannelStateHandler()
14
     private val handler = ChannelStateHandler()
15
     private val channelStateMap = ChannelStateMap { CaseMapping.Rfc }
15
     private val channelStateMap = ChannelStateMap { CaseMapping.Rfc }
16
-    private val serverState = ServerState("", "")
16
+    private val serverState = ServerState("", "", emptyList())
17
     private val ircClient = mock<IrcClient> {
17
     private val ircClient = mock<IrcClient> {
18
         on { serverState } doReturn serverState
18
         on { serverState } doReturn serverState
19
         on { channelState } doReturn channelStateMap
19
         on { channelState } doReturn channelStateMap

+ 1
- 1
src/test/kotlin/com/dmdirc/ktirc/events/EventUtilsTest.kt View File

16
 
16
 
17
 internal class EventUtilsTest {
17
 internal class EventUtilsTest {
18
 
18
 
19
-    private val serverState = ServerState("", "")
19
+    private val serverState = ServerState("", "", emptyList())
20
     private val ircClient = mock<IrcClient> {
20
     private val ircClient = mock<IrcClient> {
21
         on { serverState } doReturn serverState
21
         on { serverState } doReturn serverState
22
         on { caseMapping } doReturn CaseMapping.Ascii
22
         on { caseMapping } doReturn CaseMapping.Ascii

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

11
 
11
 
12
 internal class ServerStateHandlerTest {
12
 internal class ServerStateHandlerTest {
13
 
13
 
14
-    private val serverState = ServerState("", "")
14
+    private val serverState = ServerState("", "", emptyList())
15
     private val ircClient = mock<IrcClient> {
15
     private val ircClient = mock<IrcClient> {
16
         on { serverState } doReturn serverState
16
         on { serverState } doReturn serverState
17
     }
17
     }

+ 1
- 1
src/test/kotlin/com/dmdirc/ktirc/events/UserStateHandlerTest.kt View File

14
 
14
 
15
 internal class UserStateHandlerTest {
15
 internal class UserStateHandlerTest {
16
 
16
 
17
-    private val serverState = ServerState("", "")
17
+    private val serverState = ServerState("", "", emptyList())
18
     private val userState = UserState { CaseMapping.Rfc }
18
     private val userState = UserState { CaseMapping.Rfc }
19
 
19
 
20
     private val ircClient = mock<IrcClient> {
20
     private val ircClient = mock<IrcClient> {

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

7
 
7
 
8
     @Test
8
     @Test
9
     fun `ServerState should use the initial nickname as local nickname`() {
9
     fun `ServerState should use the initial nickname as local nickname`() {
10
-        val serverState = ServerState("acidBurn", "")
10
+        val serverState = ServerState("acidBurn", "", emptyList())
11
         assertEquals("acidBurn", serverState.localNickname)
11
         assertEquals("acidBurn", serverState.localNickname)
12
     }
12
     }
13
 
13
 
14
     @Test
14
     @Test
15
     fun `ServerState should use the initial name as server name`() {
15
     fun `ServerState should use the initial name as server name`() {
16
-        val serverState = ServerState("", "the.gibson")
16
+        val serverState = ServerState("", "the.gibson", emptyList())
17
         assertEquals("the.gibson", serverState.serverName)
17
         assertEquals("the.gibson", serverState.serverName)
18
     }
18
     }
19
 
19
 
20
     @Test
20
     @Test
21
     fun `ServerState should default status to disconnected`() {
21
     fun `ServerState should default status to disconnected`() {
22
-        val serverState = ServerState("acidBurn", "")
22
+        val serverState = ServerState("acidBurn", "", emptyList())
23
         assertEquals(ServerStatus.Disconnected, serverState.status)
23
         assertEquals(ServerStatus.Disconnected, serverState.status)
24
     }
24
     }
25
 
25
 
26
     @Test
26
     @Test
27
     fun `returns mode type for known channel mode`() {
27
     fun `returns mode type for known channel mode`() {
28
-        val serverState = ServerState("acidBurn", "")
28
+        val serverState = ServerState("acidBurn", "", emptyList())
29
         serverState.features[ServerFeature.ChannelModes] = arrayOf("ab", "cd", "ef", "gh")
29
         serverState.features[ServerFeature.ChannelModes] = arrayOf("ab", "cd", "ef", "gh")
30
         assertEquals(ChannelModeType.List, serverState.channelModeType('a'))
30
         assertEquals(ChannelModeType.List, serverState.channelModeType('a'))
31
         assertEquals(ChannelModeType.SetUnsetParameter, serverState.channelModeType('d'))
31
         assertEquals(ChannelModeType.SetUnsetParameter, serverState.channelModeType('d'))
35
 
35
 
36
     @Test
36
     @Test
37
     fun `returns whether a mode is a channel user mode or not`() {
37
     fun `returns whether a mode is a channel user mode or not`() {
38
-        val serverState = ServerState("acidBurn", "")
38
+        val serverState = ServerState("acidBurn", "", emptyList())
39
         serverState.features[ServerFeature.ModePrefixes] = ModePrefixMapping("oqv", "@~+")
39
         serverState.features[ServerFeature.ModePrefixes] = ModePrefixMapping("oqv", "@~+")
40
         assertTrue(serverState.isChannelUserMode('o'))
40
         assertTrue(serverState.isChannelUserMode('o'))
41
         assertTrue(serverState.isChannelUserMode('q'))
41
         assertTrue(serverState.isChannelUserMode('q'))
47
 
47
 
48
     @Test
48
     @Test
49
     fun `returns NoParameter for unknown channel mode`() {
49
     fun `returns NoParameter for unknown channel mode`() {
50
-        val serverState = ServerState("acidBurn", "")
50
+        val serverState = ServerState("acidBurn", "", emptyList())
51
         serverState.features[ServerFeature.ChannelModes] = arrayOf("ab", "cd", "ef", "gh")
51
         serverState.features[ServerFeature.ChannelModes] = arrayOf("ab", "cd", "ef", "gh")
52
         assertEquals(ChannelModeType.NoParameter, serverState.channelModeType('z'))
52
         assertEquals(ChannelModeType.NoParameter, serverState.channelModeType('z'))
53
     }
53
     }
54
 
54
 
55
     @Test
55
     @Test
56
     fun `returns NoParameter for channel modes if feature doesn't exist`() {
56
     fun `returns NoParameter for channel modes if feature doesn't exist`() {
57
-        val serverState = ServerState("acidBurn", "")
57
+        val serverState = ServerState("acidBurn", "", emptyList())
58
         assertEquals(ChannelModeType.NoParameter, serverState.channelModeType('b'))
58
         assertEquals(ChannelModeType.NoParameter, serverState.channelModeType('b'))
59
     }
59
     }
60
 
60
 
61
     @Test
61
     @Test
62
-    fun `reset clears all state`() = with(ServerState("acidBurn", "")) {
62
+    fun `reset clears all state`() = with(ServerState("acidBurn", "", emptyList())) {
63
         receivedWelcome = true
63
         receivedWelcome = true
64
         status = ServerStatus.Connecting
64
         status = ServerStatus.Connecting
65
         localNickname = "acidBurn3"
65
         localNickname = "acidBurn3"

+ 5
- 4
src/test/kotlin/com/dmdirc/ktirc/sasl/PlainMechanismTest.kt View File

1
 package com.dmdirc.ktirc.sasl
1
 package com.dmdirc.ktirc.sasl
2
 
2
 
3
 import com.dmdirc.ktirc.IrcClient
3
 import com.dmdirc.ktirc.IrcClient
4
-import com.dmdirc.ktirc.model.Profile
4
+import com.dmdirc.ktirc.SaslConfig
5
 import com.dmdirc.ktirc.model.ServerState
5
 import com.dmdirc.ktirc.model.ServerState
6
 import com.nhaarman.mockitokotlin2.argumentCaptor
6
 import com.nhaarman.mockitokotlin2.argumentCaptor
7
 import com.nhaarman.mockitokotlin2.doReturn
7
 import com.nhaarman.mockitokotlin2.doReturn
13
 internal class PlainMechanismTest {
13
 internal class PlainMechanismTest {
14
 
14
 
15
     private val serverState = ServerState("", "", emptyList())
15
     private val serverState = ServerState("", "", emptyList())
16
-    private val saslProfile = Profile("acidBurn", "Kate Libby", "acidB", "acidB", "HackThePlan3t!")
17
     private val ircClient = mock<IrcClient> {
16
     private val ircClient = mock<IrcClient> {
18
         on { serverState } doReturn serverState
17
         on { serverState } doReturn serverState
19
-        on { profile } doReturn saslProfile
20
     }
18
     }
21
 
19
 
22
-    private val mechanism = PlainMechanism()
20
+    private val mechanism = PlainMechanism(SaslConfig().apply {
21
+        username = "acidB"
22
+        password = "HackThePlan3t!"
23
+    })
23
 
24
 
24
     @Test
25
     @Test
25
     fun `sends encoded username and password when first message received`() {
26
     fun `sends encoded username and password when first message received`() {

Loading…
Cancel
Save