diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/SessionInitSerializerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/SessionInitSerializerTest.kt
index 5ed053c99fdc225d7f3164cb35df5751714eb3cc..6473ae26e88bee6ef3f4b7f8d74f2e5922e4f314 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/SessionInitSerializerTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/handshake/SessionInitSerializerTest.kt
@@ -15,7 +15,7 @@ import de.justjanne.libquassel.protocol.models.flags.BufferType
 import de.justjanne.libquassel.protocol.models.ids.BufferId
 import de.justjanne.libquassel.protocol.models.ids.IdentityId
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
-import de.justjanne.libquassel.protocol.syncables.Identity
+import de.justjanne.libquassel.protocol.syncables.common.Identity
 import de.justjanne.libquassel.protocol.syncables.state.IdentityState
 import de.justjanne.libquassel.protocol.testutil.byteBufferOf
 import de.justjanne.libquassel.protocol.testutil.handshakeSerializerTest
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcChannelSerializerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcChannelSerializerTest.kt
index 996edcc334f5c3d569a8e07622f173e5da08face..c4b150f47b08f08a4606cba174bee6ec18f04f82 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcChannelSerializerTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcChannelSerializerTest.kt
@@ -10,7 +10,7 @@ package de.justjanne.libquassel.protocol.serializers.quassel
 
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
 import de.justjanne.libquassel.protocol.models.types.QuasselType
-import de.justjanne.libquassel.protocol.syncables.IrcChannel
+import de.justjanne.libquassel.protocol.syncables.common.IrcChannel
 import de.justjanne.libquassel.protocol.syncables.state.IrcChannelState
 import de.justjanne.libquassel.protocol.testutil.byteBufferOf
 import de.justjanne.libquassel.protocol.testutil.matchers.MapMatcher
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcUserSerializerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcUserSerializerTest.kt
index 14436c35254d498df71d7d779ef56fb0889359d7..489000aa46212ee635716c4a6ac4dc4a147d69b7 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcUserSerializerTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcUserSerializerTest.kt
@@ -10,7 +10,7 @@ package de.justjanne.libquassel.protocol.serializers.quassel
 
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
 import de.justjanne.libquassel.protocol.models.types.QuasselType
-import de.justjanne.libquassel.protocol.syncables.IrcUser
+import de.justjanne.libquassel.protocol.syncables.common.IrcUser
 import de.justjanne.libquassel.protocol.syncables.state.IrcUserState
 import de.justjanne.libquassel.protocol.testutil.byteBufferOf
 import de.justjanne.libquassel.protocol.testutil.matchers.MapMatcher
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitDataSerializerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitDataSerializerTest.kt
index 3582d6b19c7499fd10ab20e89a5b24be0e182703..a92699cf93eeed2f95acdca6f5a88794ba064970 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitDataSerializerTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/InitDataSerializerTest.kt
@@ -10,7 +10,7 @@ package de.justjanne.libquassel.protocol.serializers.signalproxy
 
 import de.justjanne.libquassel.protocol.models.SignalProxyMessage
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
-import de.justjanne.libquassel.protocol.syncables.Network
+import de.justjanne.libquassel.protocol.syncables.common.Network
 import de.justjanne.libquassel.protocol.syncables.state.NetworkState
 import de.justjanne.libquassel.protocol.testutil.byteBufferOf
 import de.justjanne.libquassel.protocol.testutil.signalProxySerializerTest
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/SyncSerializerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/SyncSerializerTest.kt
index 73162e0a53d128a00abb0c648a197693bd08e595..cd32c9a541776fae7e80810b5c563088da58bd1c 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/SyncSerializerTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/serializers/signalproxy/SyncSerializerTest.kt
@@ -11,7 +11,7 @@ package de.justjanne.libquassel.protocol.serializers.signalproxy
 import de.justjanne.libquassel.protocol.models.SignalProxyMessage
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
 import de.justjanne.libquassel.protocol.models.types.QtType
-import de.justjanne.libquassel.protocol.syncables.Network
+import de.justjanne.libquassel.protocol.syncables.common.Network
 import de.justjanne.libquassel.protocol.syncables.state.NetworkState
 import de.justjanne.libquassel.protocol.testutil.byteBufferOf
 import de.justjanne.libquassel.protocol.testutil.signalProxySerializerTest
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/AliasManagerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/AliasManagerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..376bb0a7d68604a39f35e6207f04e685d101cec0
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/AliasManagerTest.kt
@@ -0,0 +1,620 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.bitflags.of
+import de.justjanne.libquassel.protocol.models.BufferInfo
+import de.justjanne.libquassel.protocol.models.QStringList
+import de.justjanne.libquassel.protocol.models.alias.Alias
+import de.justjanne.libquassel.protocol.models.alias.Command
+import de.justjanne.libquassel.protocol.models.flags.BufferType
+import de.justjanne.libquassel.protocol.models.ids.BufferId
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.common.AliasManager
+import de.justjanne.libquassel.protocol.syncables.state.AliasManagerState
+import de.justjanne.libquassel.protocol.testutil.mocks.EmptySession
+import de.justjanne.libquassel.protocol.testutil.mocks.RealisticSession
+import de.justjanne.libquassel.protocol.testutil.nextAliasManager
+import de.justjanne.libquassel.protocol.testutil.nextString
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import de.justjanne.libquassel.protocol.variant.qVariant
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertFalse
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import kotlin.random.Random
+
+class AliasManagerTest {
+  @Test
+  fun testEmpty() {
+    val actual = AliasManager().apply {
+      update(emptyMap())
+    }.state()
+
+    assertEquals(AliasManagerState(), actual)
+  }
+
+  @Test
+  fun testSerialization() {
+    val random = Random(1337)
+    val expected = random.nextAliasManager()
+
+    val actual = AliasManager().apply {
+      update(AliasManager(state = expected).toVariantMap())
+    }.state()
+
+    assertEquals(expected, actual)
+  }
+
+  @Test
+  fun testInvalidData() {
+    val state = AliasManagerState()
+    AliasManager(state = state).apply {
+      assertThrows<IllegalArgumentException> {
+        update(
+          mapOf(
+            "Aliases" to qVariant<QVariantMap>(
+              mapOf(
+                "names" to qVariant(emptyList(), QtType.QStringList),
+                "expansions" to qVariant<QStringList>(listOf(""), QtType.QStringList),
+              ),
+              QtType.QVariantMap
+            )
+          )
+        )
+      }
+    }
+  }
+
+  @Nested
+  inner class AddAlias {
+    @Test
+    fun new() {
+      val random = Random(1337)
+      val value = AliasManager(
+        state = random.nextAliasManager()
+      )
+
+      val aliasCount = value.aliases().size
+      val alias = Alias(random.nextString(), random.nextString())
+      assertFalse(value.aliases().contains(alias))
+      value.addAlias(alias.name, alias.expansion)
+      assertEquals(aliasCount + 1, value.aliases().size)
+      assertTrue(value.aliases().contains(alias))
+      assertEquals(aliasCount, value.indexOf(alias.name))
+    }
+
+    @Test
+    fun existing() {
+      val random = Random(1337)
+      val value = AliasManager(
+        state = random.nextAliasManager()
+      )
+
+      val aliasCount = value.aliases().size
+      val alias = value.aliases().first()
+      assertTrue(value.aliases().contains(alias))
+      value.addAlias(alias.name, alias.expansion)
+      assertEquals(aliasCount, value.aliases().size)
+      assertTrue(value.aliases().contains(alias))
+    }
+  }
+
+  @Nested
+  inner class Expansion {
+    @Test
+    fun plaintext() {
+      testExpansion(
+        null,
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "this is some text",
+        "/SAY this is some text"
+      )
+
+      testExpansion(
+        EmptySession(),
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "this is some text",
+        "/SAY this is some text"
+      )
+
+      testExpansion(
+        RealisticSession(),
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "this is some text",
+        "/SAY this is some text"
+      )
+    }
+
+    @Test
+    fun say() {
+      testExpansion(
+        null,
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/say this is some text",
+        "/say this is some text"
+      )
+
+      testExpansion(
+        EmptySession(),
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/say this is some text",
+        "/say this is some text"
+      )
+
+      testExpansion(
+        RealisticSession(),
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/say this is some text",
+        "/say this is some text"
+      )
+    }
+
+    @Test
+    fun userExpansionWithIdentd() {
+      testExpansion(
+        null,
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/userexpansion justJanne",
+        "justJanne * * * *"
+      )
+
+      testExpansion(
+        EmptySession(),
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/userexpansion justJanne",
+        "justJanne * * * *"
+      )
+
+      testExpansion(
+        RealisticSession(),
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/userexpansion justJanne",
+        "justJanne justJanne kuschku.de kuschku kuschku"
+      )
+    }
+
+    @Test
+    fun userExpansionNoIdentd() {
+      testExpansion(
+        null,
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/userexpansion digitalcircuit",
+        "digitalcircuit * * * *"
+      )
+
+      testExpansion(
+        EmptySession(),
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/userexpansion digitalcircuit",
+        "digitalcircuit * * * *"
+      )
+
+      testExpansion(
+        RealisticSession(),
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/userexpansion digitalcircuit",
+        "digitalcircuit digitalcircuit 2605:6000:1518:830d:ec4:7aff:fe6b:c6b0 * ~quassel"
+      )
+    }
+
+    @Test
+    fun userExpansionUnknownUser() {
+      testExpansion(
+        null,
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/userexpansion ChanServ",
+        "ChanServ * * * *"
+      )
+
+      testExpansion(
+        EmptySession(),
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/userexpansion ChanServ",
+        "ChanServ * * * *"
+      )
+
+      testExpansion(
+        RealisticSession(),
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/userexpansion ChanServ",
+        "ChanServ * * * *"
+      )
+    }
+
+    @Test
+    fun userExpansionNoParams() {
+      testExpansion(
+        null,
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/userexpansion",
+        "\$1 \$1:account \$1:hostname \$1:identd \$1:ident"
+      )
+
+      testExpansion(
+        EmptySession(),
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/userexpansion",
+        "\$1 \$1:account \$1:hostname \$1:identd \$1:ident"
+      )
+
+      testExpansion(
+        RealisticSession(),
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/userexpansion",
+        "\$1 \$1:account \$1:hostname \$1:identd \$1:ident"
+      )
+    }
+
+    @Test
+    fun userExpansionQuery() {
+      testExpansion(
+        null,
+        BufferInfo(
+          bufferId = BufferId(3),
+          networkId = NetworkId(1),
+          bufferName = "digitalcircuit",
+          type = BufferType.of(BufferType.Query)
+        ),
+        "/userexpansion digitalcircuit",
+        "digitalcircuit * * * *"
+      )
+
+      testExpansion(
+        EmptySession(),
+        BufferInfo(
+          bufferId = BufferId(3),
+          networkId = NetworkId(1),
+          bufferName = "digitalcircuit",
+          type = BufferType.of(BufferType.Query)
+        ),
+        "/userexpansion digitalcircuit",
+        "digitalcircuit * * * *"
+      )
+
+      testExpansion(
+        RealisticSession(),
+        BufferInfo(
+          bufferId = BufferId(3),
+          networkId = NetworkId(1),
+          bufferName = "digitalcircuit",
+          type = BufferType.of(BufferType.Query)
+        ),
+        "/userexpansion digitalcircuit",
+        "digitalcircuit digitalcircuit 2605:6000:1518:830d:ec4:7aff:fe6b:c6b0 * ~quassel"
+      )
+    }
+
+    @Test
+    fun channelExpansionChannel() {
+      testExpansion(
+        null,
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/constantexpansion 12 3",
+        "#quassel-test \$currentnick \$network 12 3"
+      )
+
+      testExpansion(
+        EmptySession(),
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/constantexpansion 12 3",
+        "#quassel-test \$currentnick \$network 12 3"
+      )
+
+      testExpansion(
+        RealisticSession(),
+        BufferInfo(
+          bufferId = BufferId(1),
+          networkId = NetworkId(1),
+          bufferName = "#quassel-test",
+          type = BufferType.of(BufferType.Channel)
+        ),
+        "/constantexpansion 12 3",
+        "#quassel-test justJanne FreeNode 12 3"
+      )
+    }
+
+    @Test
+    fun channelExpansionQuery() {
+      testExpansion(
+        null,
+        BufferInfo(
+          bufferId = BufferId(3),
+          networkId = NetworkId(1),
+          bufferName = "digitalcircuit",
+          type = BufferType.of(BufferType.Query)
+        ),
+        "/constantexpansion 12 3",
+        "digitalcircuit \$currentnick \$network 12 3"
+      )
+
+      testExpansion(
+        EmptySession(),
+        BufferInfo(
+          bufferId = BufferId(3),
+          networkId = NetworkId(1),
+          bufferName = "digitalcircuit",
+          type = BufferType.of(BufferType.Query)
+        ),
+        "/constantexpansion 12 3",
+        "digitalcircuit \$currentnick \$network 12 3"
+      )
+
+      testExpansion(
+        RealisticSession(),
+        BufferInfo(
+          bufferId = BufferId(3),
+          networkId = NetworkId(1),
+          bufferName = "digitalcircuit",
+          type = BufferType.of(BufferType.Query)
+        ),
+        "/constantexpansion 12 3",
+        "digitalcircuit justJanne FreeNode 12 3"
+      )
+    }
+
+    @Test
+    fun rangeExpansion() {
+      testExpansion(
+        null,
+        BufferInfo(
+          bufferId = BufferId(3),
+          networkId = NetworkId(1),
+          bufferName = "digitalcircuit",
+          type = BufferType.of(BufferType.Query)
+        ),
+        "/rangeexpansion a b c d e f",
+        "1 \"a\" 2 \"b\" 3..4 \"c d\" 3.. \"c d e f\""
+      )
+
+      testExpansion(
+        EmptySession(),
+        BufferInfo(
+          bufferId = BufferId(3),
+          networkId = NetworkId(1),
+          bufferName = "digitalcircuit",
+          type = BufferType.of(BufferType.Query)
+        ),
+        "/rangeexpansion a b c d e f",
+        "1 \"a\" 2 \"b\" 3..4 \"c d\" 3.. \"c d e f\""
+      )
+
+      testExpansion(
+        RealisticSession(),
+        BufferInfo(
+          bufferId = BufferId(3),
+          networkId = NetworkId(1),
+          bufferName = "digitalcircuit",
+          type = BufferType.of(BufferType.Query)
+        ),
+        "/rangeexpansion a b c d e f",
+        "1 \"a\" 2 \"b\" 3..4 \"c d\" 3.. \"c d e f\""
+      )
+    }
+
+    private fun testExpansion(
+      session: Session?,
+      buffer: BufferInfo,
+      message: String,
+      expected: String
+    ) {
+      val value = AliasManager(
+        session = session,
+        state = AliasManagerState(
+          aliases = listOf(
+            Alias(
+              "userexpansion",
+              "$1 $1:account $1:hostname $1:identd $1:ident"
+            ),
+            Alias(
+              "constantexpansion",
+              "\$channel \$currentnick \$network \$0"
+            ),
+            Alias(
+              "rangeexpansion",
+              "1 \"\$1\" 2 \"\$2\" 3..4 \"\$3..4\" 3.. \"\$3..\""
+            )
+          )
+        )
+      )
+
+      assertEquals(
+        listOf(
+          Command(
+            buffer,
+            expected
+          )
+        ),
+        value.processInput(
+          buffer,
+          message
+        )
+      )
+
+      assertEquals(
+        listOf(
+          Command(
+            buffer,
+            expected
+          )
+        ),
+        mutableListOf<Command>().also {
+          value.processInput(
+            buffer,
+            message,
+            it
+          )
+        }
+      )
+    }
+  }
+
+  @Nested
+  inner class DetermineMessageCommand {
+    @Test
+    fun plaintext() {
+      assertEquals(
+        Pair(null, "just some plain text"),
+        AliasManagerState.determineMessageCommand("just some plain text")
+      )
+    }
+
+    @Test
+    fun escaped() {
+      assertEquals(
+        Pair(null, "/escaped content"),
+        AliasManagerState.determineMessageCommand("//escaped content")
+      )
+    }
+
+    @Test
+    fun path() {
+      assertEquals(
+        Pair(null, "/bin/rm --rf /* is fun"),
+        AliasManagerState.determineMessageCommand("/bin/rm --rf /* is fun")
+      )
+    }
+
+    @Test
+    fun fakeItalic() {
+      assertEquals(
+        Pair(null, "/ suuuure /"),
+        AliasManagerState.determineMessageCommand("/ suuuure /")
+      )
+    }
+
+    @Test
+    fun command() {
+      assertEquals(
+        Pair("command", ""),
+        AliasManagerState.determineMessageCommand("/command")
+      )
+    }
+
+    @Test
+    fun commandWithParam() {
+      assertEquals(
+        Pair("command", "parameters are nice"),
+        AliasManagerState.determineMessageCommand("/command parameters are nice")
+      )
+    }
+
+    @Test
+    fun commandWithWhitespace() {
+      assertEquals(
+        Pair("command", " parameters are nice"),
+        AliasManagerState.determineMessageCommand("/command  parameters are nice")
+      )
+    }
+  }
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfigTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfigTest.kt
index d4c68d4c1379f61ebcb10ac3568220fdca2b7761..bf8ce11e9437ee2258bbdc043e720386a9ab4a5b 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfigTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfigTest.kt
@@ -15,6 +15,7 @@ import de.justjanne.libquassel.protocol.models.BufferActivity
 import de.justjanne.libquassel.protocol.models.flags.BufferType
 import de.justjanne.libquassel.protocol.models.ids.BufferId
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.syncables.common.BufferViewConfig
 import de.justjanne.libquassel.protocol.syncables.state.BufferViewConfigState
 import de.justjanne.libquassel.protocol.testutil.nextBufferViewConfig
 import org.junit.jupiter.api.Assertions.assertEquals
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewManagerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewManagerTest.kt
index 281fcaeade5b6f50929f5dc033f49abb20e11e87..0493a3a2fe66c7319b2f5be0b2daef2c5a75d2d2 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewManagerTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewManagerTest.kt
@@ -9,6 +9,7 @@
 
 package de.justjanne.libquassel.protocol.syncables
 
+import de.justjanne.libquassel.protocol.syncables.common.BufferViewManager
 import de.justjanne.libquassel.protocol.syncables.state.BufferViewManagerState
 import de.justjanne.libquassel.protocol.testutil.nextBufferViewManager
 import org.junit.jupiter.api.Assertions.assertEquals
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/HighlightRuleManagerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/HighlightRuleManagerTest.kt
index 7073f57adce4e9707f6abccfdd4b6c0f85351972..0875c98a0ecbc3aefe545358d801c2cfe489854d 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/HighlightRuleManagerTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/HighlightRuleManagerTest.kt
@@ -13,6 +13,7 @@ import de.justjanne.libquassel.protocol.models.QStringList
 import de.justjanne.libquassel.protocol.models.rules.HighlightNickType
 import de.justjanne.libquassel.protocol.models.rules.HighlightRule
 import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.common.HighlightRuleManager
 import de.justjanne.libquassel.protocol.syncables.state.HighlightRuleManagerState
 import de.justjanne.libquassel.protocol.testutil.nextEnum
 import de.justjanne.libquassel.protocol.testutil.nextHighlightRule
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IgnoreListManagerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IgnoreListManagerTest.kt
index 9f38d5d9de4441761cca2c958e28f48e0c207bfb..2ede1b74273803cc9b358f99471723485c82ed57 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IgnoreListManagerTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IgnoreListManagerTest.kt
@@ -15,6 +15,7 @@ import de.justjanne.libquassel.protocol.models.rules.IgnoreType
 import de.justjanne.libquassel.protocol.models.rules.ScopeType
 import de.justjanne.libquassel.protocol.models.rules.StrictnessType
 import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.common.IgnoreListManager
 import de.justjanne.libquassel.protocol.syncables.state.IgnoreListManagerState
 import de.justjanne.libquassel.protocol.testutil.nextIgnoreRule
 import de.justjanne.libquassel.protocol.variant.QVariantList
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannelTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannelTest.kt
index 7616b8cb60bbe6aad4a41a741033ede53078703e..2e5927945148fc818a8062b4f40bff0e75925138 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannelTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannelTest.kt
@@ -11,8 +11,11 @@ package de.justjanne.libquassel.protocol.syncables
 
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
 import de.justjanne.libquassel.protocol.models.network.ChannelModes
+import de.justjanne.libquassel.protocol.syncables.common.IrcChannel
+import de.justjanne.libquassel.protocol.syncables.common.IrcUser
+import de.justjanne.libquassel.protocol.syncables.common.Network
 import de.justjanne.libquassel.protocol.syncables.state.IrcChannelState
-import de.justjanne.libquassel.protocol.testutil.MockSession
+import de.justjanne.libquassel.protocol.testutil.mocks.EmptySession
 import de.justjanne.libquassel.protocol.testutil.nextIrcChannel
 import de.justjanne.libquassel.protocol.testutil.nextNetwork
 import org.junit.jupiter.api.Assertions.assertEquals
@@ -651,7 +654,7 @@ class IrcChannelTest {
     }
   }
 
-  class ChannelMockSession : MockSession() {
+  class ChannelMockSession : EmptySession() {
     val networks = mutableListOf<Network>()
     override fun network(id: NetworkId) = networks.find { it.networkId() == id }
   }
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUserTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUserTest.kt
index 15528dc226801a1dded49dd1b0963293b741589b..5bd79b968f51e2614150957ef80cf42e8610673b 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUserTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUserTest.kt
@@ -10,6 +10,7 @@
 package de.justjanne.libquassel.protocol.syncables
 
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.syncables.common.IrcUser
 import de.justjanne.libquassel.protocol.syncables.state.IrcUserState
 import de.justjanne.libquassel.protocol.testutil.nextIrcUser
 import org.junit.jupiter.api.Assertions.assertEquals
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkTest.kt
index 99caa070e9fe801d153823d03f048e5368882aee..97079bed5dc5330eb3e07ec59abab133a4c8842d 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkTest.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkTest.kt
@@ -17,8 +17,9 @@ import de.justjanne.libquassel.protocol.models.network.NetworkServer
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.models.types.QuasselType
 import de.justjanne.libquassel.protocol.serializers.qt.StringSerializerUtf8
+import de.justjanne.libquassel.protocol.syncables.common.Network
 import de.justjanne.libquassel.protocol.syncables.state.NetworkState
-import de.justjanne.libquassel.protocol.testutil.MockSession
+import de.justjanne.libquassel.protocol.testutil.mocks.EmptySession
 import de.justjanne.libquassel.protocol.testutil.nextNetwork
 import de.justjanne.libquassel.protocol.testutil.nextString
 import de.justjanne.libquassel.protocol.variant.qVariant
@@ -407,11 +408,11 @@ class NetworkTest {
       assertNotEquals(0, sizeBefore)
       val userName = random.nextString()
       assertFalse(network.nicks().contains(userName))
-      assertFalse(session.synchronizeCalls.contains(network.ircUser(userName) as SyncableObject?))
+      assertFalse(session.synchronizeCalls.contains(network.ircUser(userName) as SyncableStub?))
       network.addIrcUser(userName)
       assertEquals(sizeBefore + 1, network.ircUserCount())
       assertTrue(network.nicks().contains(userName))
-      assertTrue(session.synchronizeCalls.contains(network.ircUser(userName) as SyncableObject?))
+      assertTrue(session.synchronizeCalls.contains(network.ircUser(userName) as SyncableStub?))
     }
 
     @Test
@@ -437,10 +438,10 @@ class NetworkTest {
       assertNotEquals(0, network.ircUserCount())
       val user = network.ircUsers().first()
       assertTrue(network.nicks().contains(user.nick()))
-      assertFalse(session.synchronizeCalls.contains(network.ircUser(user.nick()) as SyncableObject?))
+      assertFalse(session.synchronizeCalls.contains(network.ircUser(user.nick()) as SyncableStub?))
       assertEquals(user, network.newIrcUser(user.hostMask()))
       assertTrue(network.nicks().contains(user.nick()))
-      assertFalse(session.synchronizeCalls.contains(network.ircUser(user.nick()) as SyncableObject?))
+      assertFalse(session.synchronizeCalls.contains(network.ircUser(user.nick()) as SyncableStub?))
     }
 
     @Test
@@ -481,11 +482,11 @@ class NetworkTest {
       val sizeBefore = network.ircChannelCount()
       assertNotEquals(0, sizeBefore)
       val channelName = random.nextString()
-      assertFalse(session.synchronizeCalls.contains(network.ircChannel(channelName) as SyncableObject?))
+      assertFalse(session.synchronizeCalls.contains(network.ircChannel(channelName) as SyncableStub?))
       network.addIrcChannel(channelName)
       assertEquals(sizeBefore + 1, network.ircChannelCount())
       assertTrue(network.channels().contains(channelName))
-      assertTrue(session.synchronizeCalls.contains(network.ircChannel(channelName) as SyncableObject?))
+      assertTrue(session.synchronizeCalls.contains(network.ircChannel(channelName) as SyncableStub?))
     }
     @Test
     fun addNewOffline() {
@@ -508,9 +509,9 @@ class NetworkTest {
 
       assertNotEquals(0, network.ircUserCount())
       val existing = network.ircChannels().first()
-      assertFalse(session.synchronizeCalls.contains(network.ircChannel(existing.name()) as SyncableObject?))
+      assertFalse(session.synchronizeCalls.contains(network.ircChannel(existing.name()) as SyncableStub?))
       assertEquals(existing, network.newIrcChannel(existing.name()))
-      assertFalse(session.synchronizeCalls.contains(network.ircChannel(existing.name()) as SyncableObject?))
+      assertFalse(session.synchronizeCalls.contains(network.ircChannel(existing.name()) as SyncableStub?))
     }
 
     @Test
@@ -914,11 +915,11 @@ class NetworkTest {
     }
   }
 
-  class NetworkMockSession : MockSession() {
-    val synchronizeCalls = mutableListOf<SyncableObject>()
+  class NetworkMockSession : EmptySession() {
+    val synchronizeCalls = mutableListOf<SyncableStub>()
 
-    override fun synchronize(it: SyncableObject) {
-      synchronizeCalls.add(it)
+    override fun synchronize(syncable: SyncableStub) {
+      synchronizeCalls.add(syncable)
     }
   }
 }
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/invokers/InvokerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/invokers/InvokerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e4439c456cee064c8751ca9ae7a9857b2858606f
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/invokers/InvokerTest.kt
@@ -0,0 +1,45 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables.invokers
+
+import de.justjanne.libquassel.annotations.ProtocolSide
+import de.justjanne.libquassel.protocol.syncables.invoker.Invokers
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+
+class InvokerTest {
+  @Test
+  fun testRegistered() {
+    assertEquals(
+      setOf(
+        "AliasManager",
+        "BacklogManager",
+        "BufferSyncer",
+        "BufferViewConfig",
+        "BufferViewManager",
+        "CertManager",
+        "CoreInfo",
+        "DccConfig",
+        "HighlightRuleManager",
+        "Identity",
+        "IgnoreListManager",
+        "IrcChannel",
+        "IrcListHelper",
+        "IrcUser",
+        "NetworkConfig",
+        "Network",
+        "RpcHandler",
+        "TransferManager",
+        "Transfer"
+      ),
+      Invokers.list(ProtocolSide.CLIENT)
+    )
+  }
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/MockSession.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/MockSession.kt
deleted file mode 100644
index fc408f33a7a49852245798c77658083da4724ae9..0000000000000000000000000000000000000000
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/MockSession.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * libquassel
- * Copyright (c) 2021 Janne Mareike Koschinski
- *
- * This Source Code Form is subject to the terms of the Mozilla Public License,
- * v. 2.0. If a copy of the MPL was not distributed with this file, You can
- * obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-package de.justjanne.libquassel.protocol.testutil
-
-import de.justjanne.libquassel.annotations.ProtocolSide
-import de.justjanne.libquassel.protocol.models.SignalProxyMessage
-import de.justjanne.libquassel.protocol.models.ids.IdentityId
-import de.justjanne.libquassel.protocol.models.ids.NetworkId
-import de.justjanne.libquassel.protocol.syncables.AliasManager
-import de.justjanne.libquassel.protocol.syncables.BufferSyncer
-import de.justjanne.libquassel.protocol.syncables.BufferViewManager
-import de.justjanne.libquassel.protocol.syncables.CoreInfo
-import de.justjanne.libquassel.protocol.syncables.DccConfig
-import de.justjanne.libquassel.protocol.syncables.HighlightRuleManager
-import de.justjanne.libquassel.protocol.syncables.Identity
-import de.justjanne.libquassel.protocol.syncables.Network
-import de.justjanne.libquassel.protocol.syncables.NetworkConfig
-import de.justjanne.libquassel.protocol.syncables.ObjectRepository
-import de.justjanne.libquassel.protocol.syncables.Session
-import de.justjanne.libquassel.protocol.syncables.SyncableObject
-import de.justjanne.libquassel.protocol.syncables.stubs.BacklogManagerStub
-import de.justjanne.libquassel.protocol.syncables.stubs.IgnoreListManagerStub
-import de.justjanne.libquassel.protocol.syncables.stubs.IrcListHelperStub
-import de.justjanne.libquassel.protocol.variant.QVariantMap
-
-abstract class MockSession : Session {
-  override val protocolSide: ProtocolSide
-    get() = TODO("Mock Implementation")
-  override val objectRepository: ObjectRepository
-    get() = TODO("Mock Implementation")
-
-  override fun network(id: NetworkId): Network? = TODO("Mock Implementation")
-
-  override fun identity(id: IdentityId): Identity = TODO("Mock Implementation")
-
-  override fun aliasManager(): AliasManager = TODO("Mock Implementation")
-
-  override fun bufferSyncer(): BufferSyncer = TODO("Mock Implementation")
-
-  override fun backlogManager(): BacklogManagerStub = TODO("Mock Implementation")
-
-  override fun bufferViewManager(): BufferViewManager = TODO("Mock Implementation")
-
-  override fun ignoreListManager(): IgnoreListManagerStub = TODO("Mock Implementation")
-
-  override fun highlightRuleManager(): HighlightRuleManager = TODO("Mock Implementation")
-
-  override fun ircListHelper(): IrcListHelperStub = TODO("Mock Implementation")
-
-  override fun coreInfo(): CoreInfo = TODO("Mock Implementation")
-
-  override fun dccConfig(): DccConfig = TODO("Mock Implementation")
-
-  override fun networkConfig(): NetworkConfig = TODO("Mock Implementation")
-
-  override fun synchronize(it: SyncableObject): Unit = TODO("Mock Implementation")
-
-  override fun stopSynchronize(it: SyncableObject): Unit = TODO("Mock Implementation")
-
-  override fun emit(message: SignalProxyMessage): Unit = TODO("Mock Implementation")
-
-  override fun dispatch(message: SignalProxyMessage): Unit = TODO("Mock Implementation")
-
-  override fun dispatch(message: SignalProxyMessage.Sync): Unit = TODO("Mock Implementation")
-
-  override fun dispatch(message: SignalProxyMessage.Rpc): Unit = TODO("Mock Implementation")
-
-  override fun dispatch(message: SignalProxyMessage.InitRequest): Unit = TODO("Mock Implementation")
-
-  override fun dispatch(message: SignalProxyMessage.InitData): Unit = TODO("Mock Implementation")
-
-  override fun dispatch(message: SignalProxyMessage.HeartBeat): Unit = TODO("Mock Implementation")
-
-  override fun dispatch(message: SignalProxyMessage.HeartBeatReply): Unit = TODO("Mock Implementation")
-
-  override val className: String
-    get() = TODO("Mock Implementation")
-  override val objectName: String
-    get() = TODO("Mock Implementation")
-  override val initialized: Boolean
-    get() = TODO("Mock Implementation")
-  override val session: Session?
-    get() = TODO("Mock Implementation")
-
-  override fun fromVariantMap(properties: QVariantMap): Unit = TODO("Mock Implementation")
-
-  override fun toVariantMap(): QVariantMap = TODO("Mock Implementation")
-}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/Random.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/Random.kt
index fb29e95207f0ce659a64ebb48108927efbd4e654..f76348bb8329b616e7c346f36a3440e4bb47436b 100644
--- a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/Random.kt
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/Random.kt
@@ -11,6 +11,7 @@ package de.justjanne.libquassel.protocol.testutil
 
 import de.justjanne.bitflags.of
 import de.justjanne.libquassel.protocol.models.BufferActivity
+import de.justjanne.libquassel.protocol.models.alias.Alias
 import de.justjanne.libquassel.protocol.models.flags.BufferType
 import de.justjanne.libquassel.protocol.models.ids.BufferId
 import de.justjanne.libquassel.protocol.models.ids.IdentityId
@@ -18,9 +19,10 @@ import de.justjanne.libquassel.protocol.models.ids.NetworkId
 import de.justjanne.libquassel.protocol.models.network.NetworkServer
 import de.justjanne.libquassel.protocol.models.rules.HighlightRule
 import de.justjanne.libquassel.protocol.models.rules.IgnoreRule
-import de.justjanne.libquassel.protocol.syncables.BufferViewConfig
-import de.justjanne.libquassel.protocol.syncables.IrcChannel
-import de.justjanne.libquassel.protocol.syncables.IrcUser
+import de.justjanne.libquassel.protocol.syncables.common.BufferViewConfig
+import de.justjanne.libquassel.protocol.syncables.common.IrcChannel
+import de.justjanne.libquassel.protocol.syncables.common.IrcUser
+import de.justjanne.libquassel.protocol.syncables.state.AliasManagerState
 import de.justjanne.libquassel.protocol.syncables.state.BufferViewConfigState
 import de.justjanne.libquassel.protocol.syncables.state.BufferViewManagerState
 import de.justjanne.libquassel.protocol.syncables.state.IrcChannelState
@@ -181,6 +183,12 @@ fun Random.nextBufferViewConfig(
   }.toSet()
 )
 
+fun Random.nextAliasManager() = AliasManagerState(
+  aliases = List(nextInt(20)) {
+    Alias(nextString(), nextString())
+  }
+)
+
 fun Random.nextBufferViewManager() = BufferViewManagerState(
   bufferViewConfigs = List(nextInt(20)) {
     BufferViewConfig(state = BufferViewConfigState(bufferViewId = nextInt()))
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/mocks/EmptySession.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/mocks/EmptySession.kt
new file mode 100644
index 0000000000000000000000000000000000000000..50ff2ca631519dc468cc162d649eb5793f26ef05
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/mocks/EmptySession.kt
@@ -0,0 +1,61 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.testutil.mocks
+
+import de.justjanne.libquassel.annotations.ProtocolSide
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
+import de.justjanne.libquassel.protocol.models.ids.IdentityId
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.syncables.ObjectRepository
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.common.AliasManager
+import de.justjanne.libquassel.protocol.syncables.common.BacklogManager
+import de.justjanne.libquassel.protocol.syncables.common.BufferSyncer
+import de.justjanne.libquassel.protocol.syncables.common.BufferViewManager
+import de.justjanne.libquassel.protocol.syncables.common.CoreInfo
+import de.justjanne.libquassel.protocol.syncables.common.DccConfig
+import de.justjanne.libquassel.protocol.syncables.common.HighlightRuleManager
+import de.justjanne.libquassel.protocol.syncables.common.Identity
+import de.justjanne.libquassel.protocol.syncables.common.IgnoreListManager
+import de.justjanne.libquassel.protocol.syncables.common.IrcListHelper
+import de.justjanne.libquassel.protocol.syncables.common.Network
+import de.justjanne.libquassel.protocol.syncables.common.NetworkConfig
+import de.justjanne.libquassel.protocol.syncables.common.RpcHandler
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+
+open class EmptySession : Session {
+  override val protocolSide = ProtocolSide.CLIENT
+  override val rpcHandler = RpcHandler(this)
+  override val objectRepository = ObjectRepository()
+
+  override fun network(id: NetworkId): Network? = null
+  override fun addNetwork(id: NetworkId) = Unit
+  override fun removeNetwork(id: NetworkId) = Unit
+  override fun identity(id: IdentityId): Identity? = null
+  override fun addIdentity(properties: QVariantMap) = Unit
+  override fun removeIdentity(id: IdentityId) = Unit
+
+  override val aliasManager = AliasManager(this)
+  override val bufferSyncer = BufferSyncer(this)
+  override val backlogManager = BacklogManager(this)
+  override val bufferViewManager = BufferViewManager(this)
+  override val ignoreListManager = IgnoreListManager(this)
+  override val highlightRuleManager = HighlightRuleManager(this)
+  override val ircListHelper = IrcListHelper(this)
+  override val coreInfo = CoreInfo(this)
+  override val dccConfig = DccConfig(this)
+  override val networkConfig = NetworkConfig(this)
+
+  override fun synchronize(syncable: SyncableStub) = Unit
+  override fun stopSynchronize(syncable: SyncableStub) = Unit
+  override fun emit(message: SignalProxyMessage) = Unit
+  override fun dispatch(message: SignalProxyMessage) = Unit
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/mocks/RealisticSession.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/mocks/RealisticSession.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f2a23377f5152d74186e246204f5c55e6c15428a
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/testutil/mocks/RealisticSession.kt
@@ -0,0 +1,195 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.testutil.mocks
+
+import de.justjanne.bitflags.of
+import de.justjanne.libquassel.protocol.models.BufferInfo
+import de.justjanne.libquassel.protocol.models.flags.BufferType
+import de.justjanne.libquassel.protocol.models.ids.BufferId
+import de.justjanne.libquassel.protocol.models.ids.IdentityId
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.models.network.ChannelModes
+import de.justjanne.libquassel.protocol.models.network.ConnectionState
+import de.justjanne.libquassel.protocol.models.network.NetworkServer
+import de.justjanne.libquassel.protocol.models.network.PortDefaults
+import de.justjanne.libquassel.protocol.syncables.common.BufferSyncer
+import de.justjanne.libquassel.protocol.syncables.common.IrcChannel
+import de.justjanne.libquassel.protocol.syncables.common.IrcUser
+import de.justjanne.libquassel.protocol.syncables.common.Network
+import de.justjanne.libquassel.protocol.syncables.state.BufferSyncerState
+import de.justjanne.libquassel.protocol.syncables.state.IrcChannelState
+import de.justjanne.libquassel.protocol.syncables.state.IrcUserState
+import de.justjanne.libquassel.protocol.syncables.state.NetworkState
+import de.justjanne.libquassel.protocol.util.irc.IrcCaseMapper
+
+class RealisticSession : EmptySession() {
+  private val networks = setOf(
+    Network(
+      this,
+      NetworkState(
+        networkId = NetworkId(1),
+        networkName = "FreeNode",
+        currentServer = "tepper.freenode.net",
+        connected = true,
+        connectionState = ConnectionState.Initialized,
+        myNick = "justJanne",
+        latency = 48,
+        identity = IdentityId(1),
+        serverList = listOf(
+          NetworkServer(
+            "irc.freenode.net",
+            PortDefaults.PORT_SSL.port,
+            useSsl = true
+          ),
+          NetworkServer(
+            "chat.freenode.net",
+            PortDefaults.PORT_SSL.port,
+            useSsl = true
+          )
+        ),
+        supports = mapOf(
+          "CHANTYPES" to "#",
+          "EXCEPTS" to null,
+          "INVEX" to null,
+          "CHANMODES" to "eIbq,k,flj,CFLMPQScgimnprstz",
+          "CHANLIMIT" to "#:120",
+          "PREFIX" to "(ov)@+",
+          "MAXLIST" to "bqeI:100",
+          "MODES" to "4",
+          "NETWORK" to "freenode",
+          "STATUSMSG" to "@+",
+          "CALLERID" to "g",
+          "CASEMAPPING" to "rfc1459",
+          "CHARSET" to "ascii",
+          "NICKLEN" to "16",
+          "CHANNELLEN" to "50",
+          "TOPICLEN" to "390",
+          "DEAF" to "D",
+          "FNC" to null,
+          "TARGMAX" to "NAMES:1,LIST:1,KICK:1,WHOIS:1,PRIVMSG:4,NOTICE:4,ACCEPT:,MONITOR:",
+          "EXTBAN" to "$,ajrxz",
+          "CLIENTVER" to "3.0",
+          "ETRACE" to null,
+          "KNOCK" to null,
+          "WHOX" to null,
+          "CPRIVMSG" to null,
+          "CNOTICE" to null,
+          "SAFELIST" to null,
+          "ELIST" to "CTU",
+        ),
+        caps = mapOf(
+          "account-notify" to null,
+          "sasl" to null,
+          "identify-msg" to null,
+          "multi-prefix" to null,
+          "extended-join" to null
+        ),
+        capsEnabled = setOf(
+          "sasl",
+          "account-notify",
+          "extended-join",
+          "multi-prefix"
+        ),
+        ircUsers = setOf(
+          IrcUser(
+            this,
+            IrcUserState(
+              network = NetworkId(1),
+              nick = "justJanne",
+              user = "kuschku",
+              host = "kuschku.de",
+              realName = "Janne Mareike Koschinski <janne@kuschku.de>",
+              account = "justJanne",
+              server = "tepper.freenode.net"
+            )
+          ),
+          IrcUser(
+            this,
+            IrcUserState(
+              network = NetworkId(1),
+              nick = "digitalcircuit",
+              user = "~quassel",
+              host = "2605:6000:1518:830d:ec4:7aff:fe6b:c6b0",
+              realName = "Shane <avatar@mg.zorro.casa>",
+              account = "digitalcircuit",
+              server = "wolfe.freenode.net"
+            )
+          ),
+          IrcUser(
+            this,
+            IrcUserState(
+              network = NetworkId(1),
+              nick = "Sput",
+              user = "~sputnick",
+              host = "quassel/developer/sput",
+              realName = "Sputnick -- http://quassel-irc.org",
+              account = "Sput",
+              server = "niven.freenode.net"
+            )
+          )
+        ).associateBy { IrcCaseMapper["rfc1459"].toLowerCase(it.nick()) },
+        ircChannels = setOf(
+          IrcChannel(
+            this,
+            IrcChannelState(
+              network = NetworkId(1),
+              name = "#quassel-test",
+              topic = "Quassel testing channel",
+              channelModes = ChannelModes(
+                d = setOf('n', 't', 'c')
+              ),
+              userModes = mapOf(
+                "justjanne" to emptySet(),
+                "digitalcircuit" to emptySet(),
+                "Sput" to emptySet(),
+              )
+            )
+          )
+        ).associateBy(IrcChannel::name)
+      )
+    )
+  ).associateBy(Network::networkId)
+
+  private val buffers = setOf(
+    BufferInfo(
+      bufferId = BufferId(1),
+      networkId = NetworkId(1),
+      bufferName = "FreeNode",
+      type = BufferType.of(BufferType.Status)
+    ),
+    BufferInfo(
+      bufferId = BufferId(2),
+      networkId = NetworkId(1),
+      bufferName = "#quassel-test",
+      type = BufferType.of(BufferType.Channel)
+    ),
+    BufferInfo(
+      bufferId = BufferId(3),
+      networkId = NetworkId(1),
+      bufferName = "digitalcircuit",
+      type = BufferType.of(BufferType.Query)
+    ),
+    BufferInfo(
+      bufferId = BufferId(4),
+      networkId = NetworkId(1),
+      bufferName = "ChanServ",
+      type = BufferType.of(BufferType.Query)
+    ),
+  ).associateBy(BufferInfo::bufferId)
+
+  override fun network(id: NetworkId): Network? = networks[id]
+
+  override val bufferSyncer = BufferSyncer(
+    this,
+    BufferSyncerState(
+      bufferInfos = buffers
+    )
+  )
+}