From 25d070b8b2f11ba283cdf94e30469e2ee6963117 Mon Sep 17 00:00:00 2001
From: Janne Mareike Koschinski <janne@kuschku.de>
Date: Mon, 14 Jun 2021 17:48:33 +0200
Subject: [PATCH] Improve test coverage

---
 gradle.properties                             |   2 +-
 .../protocol/models/ConnectedClient.kt        |   2 +-
 .../syncables/common/BufferViewConfig.kt      |   2 +-
 .../protocol/syncables/common/CertManager.kt  |  59 +--
 .../protocol/syncables/common/CoreInfo.kt     |  23 +-
 .../syncables/state/CertManagerState.kt       |  39 +-
 .../protocol/syncables/BufferSyncerTest.kt    |  42 ++
 .../protocol/syncables/CertManagerTest.kt     |  42 ++
 .../protocol/syncables/CoreInfoTest.kt        |  40 ++
 .../protocol/syncables/DccConfigTest.kt       |  40 ++
 .../protocol/syncables/IdentityTest.kt        | 380 ++++++++++++++++++
 .../protocol/syncables/NetworkConfigTest.kt   | 133 ++++++
 .../protocol/syncables/NetworkTest.kt         |  98 +++--
 .../libquassel/protocol/testutil/Random.kt    | 126 +++++-
 14 files changed, 932 insertions(+), 96 deletions(-)
 create mode 100644 libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferSyncerTest.kt
 create mode 100644 libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/CertManagerTest.kt
 create mode 100644 libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/CoreInfoTest.kt
 create mode 100644 libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/DccConfigTest.kt
 create mode 100644 libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IdentityTest.kt
 create mode 100644 libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkConfigTest.kt

diff --git a/gradle.properties b/gradle.properties
index 046feed..8d42b60 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -14,7 +14,7 @@ kotlinPoetVersion=1.8.0
 kspVersion=1.5.10-1.0.0-beta01
 
 GROUP=de.justjanne.libquassel
-VERSION_NAME=0.6.1
+VERSION_NAME=0.6.2
 
 POM_URL=https://git.kuschku.de/justJanne/libquassel
 POM_SCM_URL=https://git.kuschku.de/justJanne/libquassel
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ConnectedClient.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ConnectedClient.kt
index 75edaf3..8528be1 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ConnectedClient.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/ConnectedClient.kt
@@ -37,7 +37,7 @@ data class ConnectedClient(
     "location" to qVariant(location, QtType.QString),
     "clientVersion" to qVariant(version, QtType.QString),
     "clientVersionDate" to qVariant(versionDate?.epochSecond?.toString(), QtType.QString),
-    "connectedSince" to qVariant(connectedSince, QtType.QDateTime),
+    "connectedSince" to qVariant(connectedSince.atOffset(ZoneOffset.UTC), QtType.QDateTime),
     "secure" to qVariant(secure, QtType.Bool),
     "features" to qVariant(features.legacyFeatures().toBits(), QtType.UInt),
     "featureList" to qVariant(features.featureList().map(QuasselFeatureName::name), QtType.QStringList)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferViewConfig.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferViewConfig.kt
index 769844b..ae09d4b 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferViewConfig.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferViewConfig.kt
@@ -261,7 +261,7 @@ open class BufferViewConfig(
         }
         .filter { (_, value) -> value.networkId == info.networkId }
         .find { (_, value) ->
-          String.CASE_INSENSITIVE_ORDER.compare(value.bufferName, info.bufferName) > 0
+          String.CASE_INSENSITIVE_ORDER.compare(value.bufferName, info.bufferName) >= 0
         }?.index ?: buffers().size
     )
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CertManager.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CertManager.kt
index 1c36265..4f05787 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CertManager.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CertManager.kt
@@ -19,14 +19,7 @@ import de.justjanne.libquassel.protocol.util.update
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.into
 import de.justjanne.libquassel.protocol.variant.qVariant
-import org.bouncycastle.cert.X509CertificateHolder
-import org.bouncycastle.openssl.PEMKeyPair
-import org.bouncycastle.openssl.PEMParser
-import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
 import java.nio.ByteBuffer
-import java.security.PrivateKey
-import java.security.cert.Certificate
-import java.security.cert.CertificateFactory
 
 open class CertManager(
   session: Session? = null,
@@ -38,15 +31,10 @@ open class CertManager(
   }
 
   override fun fromVariantMap(properties: QVariantMap) {
-    val privateKeyPem = properties["sslKey"].into("")
-    val certPem = properties["sslCert"].into("")
-
     state.update {
       copy(
-        privateKeyPem = privateKeyPem,
-        certificatePem = certPem,
-        privateKey = readPrivateKey(privateKeyPem),
-        certificate = readCertificate(certPem)
+        privateKeyPem = StringSerializerUtf8.deserializeRaw(properties["sslKey"].into()),
+        certificatePem = StringSerializerUtf8.deserializeRaw(properties["sslCert"].into())
       )
     }
     renameObject(state().identifier())
@@ -54,47 +42,15 @@ open class CertManager(
   }
 
   override fun toVariantMap() = mapOf(
-    "sslKey" to qVariant(StringSerializerUtf8.serializeRaw(state().certificatePem), QtType.QByteArray),
-    "sslCert" to qVariant(StringSerializerUtf8.serializeRaw(state().privateKeyPem), QtType.QByteArray)
+    "sslKey" to qVariant(StringSerializerUtf8.serializeRaw(state().privateKeyPem), QtType.QByteArray),
+    "sslCert" to qVariant(StringSerializerUtf8.serializeRaw(state().certificatePem), QtType.QByteArray)
   )
 
-  private fun readPrivateKey(pem: String): PrivateKey? {
-    if (pem.isBlank()) {
-      return null
-    }
-
-    try {
-      val keyPair = PEMParser(pem.reader()).readObject() as? PEMKeyPair
-        ?: return null
-      return JcaPEMKeyConverter().getPrivateKey(keyPair.privateKeyInfo)
-    } catch (t: Throwable) {
-      return null
-    }
-  }
-
-  private fun readCertificate(pem: String): Certificate? {
-    if (pem.isBlank()) {
-      return null
-    }
-
-    try {
-      val certificate = PEMParser(pem.reader()).readObject() as? X509CertificateHolder
-        ?: return null
-      return CertificateFactory.getInstance("X.509")
-        .generateCertificate(certificate.encoded.inputStream())
-    } catch (t: Throwable) {
-      return null
-    }
-  }
-
   override fun setSslKey(encoded: ByteBuffer) {
     val pem = StringSerializerUtf8.deserializeRaw(encoded)
 
     state.update {
-      copy(
-        privateKeyPem = pem,
-        privateKey = readPrivateKey(pem)
-      )
+      copy(privateKeyPem = pem)
     }
     super.setSslKey(encoded)
   }
@@ -103,10 +59,7 @@ open class CertManager(
     val pem = StringSerializerUtf8.deserializeRaw(encoded)
 
     state.update {
-      copy(
-        certificatePem = pem,
-        certificate = readCertificate(pem)
-      )
+      copy(certificatePem = pem)
     }
     super.setSslCert(encoded)
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CoreInfo.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CoreInfo.kt
index 5dde098..f69f65a 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CoreInfo.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CoreInfo.kt
@@ -50,15 +50,20 @@ open class CoreInfo(
   }
 
   override fun toVariantMap() = mapOf(
-    "quasselVersion" to qVariant(version(), QtType.QString),
-    "quasselBuildDate" to qVariant(versionDate()?.epochSecond?.toString(), QtType.QString),
-    "startTime" to qVariant(startTime(), QtType.QDateTime),
-    "sessionConnectedClients" to qVariant(connectedClientCount(), QtType.Int),
-    "sessionConnectedClientData" to qVariant(
-      connectedClients()
-        .map(ConnectedClient::toVariantMap)
-        .map { qVariant(it, QtType.QVariantMap) },
-      QtType.QVariantList
+    "coreData" to qVariant(
+      mapOf(
+        "quasselVersion" to qVariant(version(), QtType.QString),
+        "quasselBuildDate" to qVariant(versionDate()?.epochSecond?.toString(), QtType.QString),
+        "startTime" to qVariant(startTime().atOffset(ZoneOffset.UTC), QtType.QDateTime),
+        "sessionConnectedClients" to qVariant(connectedClientCount(), QtType.Int),
+        "sessionConnectedClientData" to qVariant(
+          connectedClients()
+            .map(ConnectedClient::toVariantMap)
+            .map { qVariant(it, QtType.QVariantMap) },
+          QtType.QVariantList
+        )
+      ),
+      QtType.QVariantMap
     )
   )
 
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/CertManagerState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/CertManagerState.kt
index ea6bb8b..4b03bf6 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/CertManagerState.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/CertManagerState.kt
@@ -10,15 +10,50 @@
 package de.justjanne.libquassel.protocol.syncables.state
 
 import de.justjanne.libquassel.protocol.models.ids.IdentityId
+import org.bouncycastle.cert.X509CertificateHolder
+import org.bouncycastle.openssl.PEMKeyPair
+import org.bouncycastle.openssl.PEMParser
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
 import java.security.PrivateKey
 import java.security.cert.Certificate
+import java.security.cert.CertificateFactory
 
 data class CertManagerState(
   val identityId: IdentityId,
   val privateKeyPem: String = "",
   val certificatePem: String = "",
-  val privateKey: PrivateKey? = null,
-  val certificate: Certificate? = null
 ) {
+  val privateKey = readPrivateKey(privateKeyPem)
+  val certificate = readCertificate(certificatePem)
+
   fun identifier() = "${identityId.id}"
+
+  private fun readPrivateKey(pem: String): PrivateKey? {
+    if (pem.isBlank()) {
+      return null
+    }
+
+    try {
+      val keyPair = PEMParser(pem.reader()).readObject() as? PEMKeyPair
+        ?: return null
+      return JcaPEMKeyConverter().getPrivateKey(keyPair.privateKeyInfo)
+    } catch (t: Throwable) {
+      return null
+    }
+  }
+
+  private fun readCertificate(pem: String): Certificate? {
+    if (pem.isBlank()) {
+      return null
+    }
+
+    try {
+      val certificate = PEMParser(pem.reader()).readObject() as? X509CertificateHolder
+        ?: return null
+      return CertificateFactory.getInstance("X.509")
+        .generateCertificate(certificate.encoded.inputStream())
+    } catch (t: Throwable) {
+      return null
+    }
+  }
 }
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferSyncerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferSyncerTest.kt
new file mode 100644
index 0000000..3b7595b
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/BufferSyncerTest.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.libquassel.protocol.syncables.common.BufferSyncer
+import de.justjanne.libquassel.protocol.syncables.state.BufferSyncerState
+import de.justjanne.libquassel.protocol.testutil.nextBufferSyncer
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import kotlin.random.Random
+
+class BufferSyncerTest {
+  @Test
+  fun testEmpty() {
+    val actual = BufferSyncer().apply {
+      update(emptyMap())
+    }.state()
+
+    assertEquals(BufferSyncerState(), actual)
+  }
+
+  @Test
+  fun testSerialization() {
+    val random = Random(1337)
+    val expected = random.nextBufferSyncer()
+      // bufferInfos are not intended to be serialized
+      .copy(bufferInfos = emptyMap())
+
+    val actual = BufferSyncer().apply {
+      update(BufferSyncer(state = expected).toVariantMap())
+    }.state()
+
+    assertEquals(expected, actual)
+  }
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/CertManagerTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/CertManagerTest.kt
new file mode 100644
index 0000000..938d8eb
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/CertManagerTest.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.libquassel.protocol.models.ids.IdentityId
+import de.justjanne.libquassel.protocol.syncables.common.CertManager
+import de.justjanne.libquassel.protocol.syncables.state.CertManagerState
+import de.justjanne.libquassel.protocol.testutil.nextCertManager
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import kotlin.random.Random
+
+class CertManagerTest {
+  @Test
+  fun testEmpty() {
+    val identityId = IdentityId(0)
+    val actual = CertManager(state = CertManagerState(identityId)).apply {
+      update(emptyMap())
+    }.state()
+
+    assertEquals(CertManagerState(identityId), actual)
+  }
+
+  @Test
+  fun testSerialization() {
+    val random = Random(1337)
+    val expected = random.nextCertManager()
+
+    val actual = CertManager(state = CertManagerState(identityId = expected.identityId)).apply {
+      update(CertManager(state = expected).toVariantMap())
+    }.state()
+
+    assertEquals(expected, actual)
+  }
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/CoreInfoTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/CoreInfoTest.kt
new file mode 100644
index 0000000..8c4080c
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/CoreInfoTest.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.libquassel.protocol.syncables.common.CoreInfo
+import de.justjanne.libquassel.protocol.syncables.state.CoreInfoState
+import de.justjanne.libquassel.protocol.testutil.nextCoreInfo
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import kotlin.random.Random
+
+class CoreInfoTest {
+  @Test
+  fun testEmpty() {
+    val actual = CoreInfo().apply {
+      update(emptyMap())
+    }.state()
+
+    assertEquals(CoreInfoState(), actual)
+  }
+
+  @Test
+  fun testSerialization() {
+    val random = Random(1337)
+    val expected = random.nextCoreInfo()
+
+    val actual = CoreInfo().apply {
+      update(CoreInfo(state = expected).toVariantMap())
+    }.state()
+
+    assertEquals(expected, actual)
+  }
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/DccConfigTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/DccConfigTest.kt
new file mode 100644
index 0000000..65697fd
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/DccConfigTest.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.libquassel.protocol.syncables.common.DccConfig
+import de.justjanne.libquassel.protocol.syncables.state.DccConfigState
+import de.justjanne.libquassel.protocol.testutil.nextDccConfig
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import kotlin.random.Random
+
+class DccConfigTest {
+  @Test
+  fun testEmpty() {
+    val actual = DccConfig().apply {
+      update(emptyMap())
+    }.state()
+
+    assertEquals(DccConfigState(), actual)
+  }
+
+  @Test
+  fun testSerialization() {
+    val random = Random(1337)
+    val expected = random.nextDccConfig()
+
+    val actual = DccConfig().apply {
+      update(DccConfig(state = expected).toVariantMap())
+    }.state()
+
+    assertEquals(expected, actual)
+  }
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IdentityTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IdentityTest.kt
new file mode 100644
index 0000000..aeb06f2
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/IdentityTest.kt
@@ -0,0 +1,380 @@
+/*
+ * 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.libquassel.protocol.models.ids.IdentityId
+import de.justjanne.libquassel.protocol.syncables.common.Identity
+import de.justjanne.libquassel.protocol.syncables.state.IdentityState
+import de.justjanne.libquassel.protocol.testutil.nextIdentity
+import de.justjanne.libquassel.protocol.testutil.nextString
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertNotEquals
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import kotlin.random.Random
+
+class IdentityTest {
+  @Test
+  fun testEmpty() {
+    val actual = Identity().apply {
+      update(emptyMap())
+    }.state()
+
+    assertEquals(IdentityState(), actual)
+  }
+
+  @Test
+  fun testSerialization() {
+    val random = Random(1337)
+    val expected = random.nextIdentity()
+
+    val actual = Identity().apply {
+      update(Identity(state = expected).toVariantMap())
+    }.state()
+
+    assertEquals(expected, actual)
+  }
+
+  @Nested
+  inner class Setters {
+    @Test
+    fun testNicks() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      val nicks = List(random.nextInt(20)) {
+        random.nextString()
+      }
+      assertNotEquals(nicks, identity.nicks())
+      identity.setNicks(nicks)
+      assertEquals(nicks, identity.nicks())
+    }
+
+    @Test
+    fun testNicksInvalid() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      val nick = random.nextString()
+      assertNotEquals(listOf(nick, ""), identity.nicks())
+      assertNotEquals(listOf(nick, null), identity.nicks())
+      identity.setNicks(listOf(nick, null))
+      assertEquals(listOf(nick, ""), identity.nicks())
+    }
+
+    @Test
+    fun testAutoAwayReason() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      val value = random.nextString()
+      assertNotEquals(value, identity.autoAwayReason())
+      identity.setAutoAwayReason(value)
+      assertEquals(value, identity.autoAwayReason())
+    }
+
+    @Test
+    fun testAutoAwayReasonInvalid() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      assertNotEquals("", identity.autoAwayReason())
+      assertNotEquals(null, identity.autoAwayReason())
+      identity.setAutoAwayReason(null)
+      assertEquals("", identity.autoAwayReason())
+    }
+
+    @Test
+    fun testAwayNick() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      val value = random.nextString()
+      assertNotEquals(value, identity.awayNick())
+      identity.setAwayNick(value)
+      assertEquals(value, identity.awayNick())
+    }
+
+    @Test
+    fun testAwayNickInvalid() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      assertNotEquals("", identity.awayNick())
+      assertNotEquals(null, identity.awayNick())
+      identity.setAwayNick(null)
+      assertEquals("", identity.awayNick())
+    }
+
+    @Test
+    fun testAwayReason() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      val value = random.nextString()
+      assertNotEquals(value, identity.awayReason())
+      identity.setAwayReason(value)
+      assertEquals(value, identity.awayReason())
+    }
+
+    @Test
+    fun testAwayReasonInvalid() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      assertNotEquals("", identity.awayReason())
+      assertNotEquals(null, identity.awayReason())
+      identity.setAwayReason(null)
+      assertEquals("", identity.awayReason())
+    }
+
+    @Test
+    fun testDetachAwayReason() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      val value = random.nextString()
+      assertNotEquals(value, identity.detachAwayReason())
+      identity.setDetachAwayReason(value)
+      assertEquals(value, identity.detachAwayReason())
+    }
+
+    @Test
+    fun testDetachAwayReasonInvalid() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      assertNotEquals("", identity.detachAwayReason())
+      assertNotEquals(null, identity.detachAwayReason())
+      identity.setDetachAwayReason(null)
+      assertEquals("", identity.detachAwayReason())
+    }
+
+    @Test
+    fun testIdent() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      val value = random.nextString()
+      assertNotEquals(value, identity.ident())
+      identity.setIdent(value)
+      assertEquals(value, identity.ident())
+    }
+
+    @Test
+    fun testIdentInvalid() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      assertNotEquals("", identity.ident())
+      assertNotEquals(null, identity.ident())
+      identity.setIdent(null)
+      assertEquals("", identity.ident())
+    }
+
+    @Test
+    fun testIdentityName() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      val value = random.nextString()
+      assertNotEquals(value, identity.identityName())
+      identity.setIdentityName(value)
+      assertEquals(value, identity.identityName())
+    }
+
+    @Test
+    fun testIdentityNameInvalid() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      assertNotEquals("", identity.identityName())
+      assertNotEquals(null, identity.identityName())
+      identity.setIdentityName(null)
+      assertEquals("", identity.identityName())
+    }
+
+    @Test
+    fun testKickReason() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      val value = random.nextString()
+      assertNotEquals(value, identity.kickReason())
+      identity.setKickReason(value)
+      assertEquals(value, identity.kickReason())
+    }
+
+    @Test
+    fun testKickReasonInvalid() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      assertNotEquals("", identity.kickReason())
+      assertNotEquals(null, identity.kickReason())
+      identity.setKickReason(null)
+      assertEquals("", identity.kickReason())
+    }
+
+    @Test
+    fun testPartReason() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      val value = random.nextString()
+      assertNotEquals(value, identity.partReason())
+      identity.setPartReason(value)
+      assertEquals(value, identity.partReason())
+    }
+
+    @Test
+    fun testPartReasonInvalid() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      assertNotEquals("", identity.partReason())
+      assertNotEquals(null, identity.partReason())
+      identity.setPartReason(null)
+      assertEquals("", identity.partReason())
+    }
+
+    @Test
+    fun testQuitReason() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      val value = random.nextString()
+      assertNotEquals(value, identity.quitReason())
+      identity.setQuitReason(value)
+      assertEquals(value, identity.quitReason())
+    }
+
+    @Test
+    fun testQuitReasonInvalid() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      assertNotEquals("", identity.quitReason())
+      assertNotEquals(null, identity.quitReason())
+      identity.setQuitReason(null)
+      assertEquals("", identity.quitReason())
+    }
+
+    @Test
+    fun testRealName() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      val value = random.nextString()
+      assertNotEquals(value, identity.realName())
+      identity.setRealName(value)
+      assertEquals(value, identity.realName())
+    }
+
+    @Test
+    fun testRealNameInvalid() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      assertNotEquals("", identity.realName())
+      assertNotEquals(null, identity.realName())
+      identity.setRealName(null)
+      assertEquals("", identity.realName())
+    }
+
+    @Test
+    fun testAutoAwayEnabled() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      identity.setAutoAwayEnabled(false)
+      assertEquals(false, identity.autoAwayEnabled())
+      identity.setAutoAwayEnabled(true)
+      assertEquals(true, identity.autoAwayEnabled())
+    }
+
+    @Test
+    fun testAutoAwayReasonEnabled() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      identity.setAutoAwayReasonEnabled(false)
+      assertEquals(false, identity.autoAwayReasonEnabled())
+      identity.setAutoAwayReasonEnabled(true)
+      assertEquals(true, identity.autoAwayReasonEnabled())
+    }
+
+    @Test
+    fun testAwayNickEnabled() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      identity.setAwayNickEnabled(false)
+      assertEquals(false, identity.awayNickEnabled())
+      identity.setAwayNickEnabled(true)
+      assertEquals(true, identity.awayNickEnabled())
+    }
+
+    @Test
+    fun testAwayReasonEnabled() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      identity.setAwayReasonEnabled(false)
+      assertEquals(false, identity.awayReasonEnabled())
+      identity.setAwayReasonEnabled(true)
+      assertEquals(true, identity.awayReasonEnabled())
+    }
+
+    @Test
+    fun testDetachAwayReasonEnabled() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      identity.setDetachAwayReasonEnabled(false)
+      assertEquals(false, identity.detachAwayReasonEnabled())
+      identity.setDetachAwayReasonEnabled(true)
+      assertEquals(true, identity.detachAwayReasonEnabled())
+    }
+
+    @Test
+    fun testDetachAwayEnabled() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      identity.setDetachAwayEnabled(false)
+      assertEquals(false, identity.detachAwayEnabled())
+      identity.setDetachAwayEnabled(true)
+      assertEquals(true, identity.detachAwayEnabled())
+    }
+
+    @Test
+    fun testAutoAwayTime() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      val value = random.nextInt()
+      assertNotEquals(value, identity.autoAwayTime())
+      identity.setAutoAwayTime(value)
+      assertEquals(value, identity.autoAwayTime())
+    }
+
+    @Test
+    fun testId() {
+      val random = Random(1337)
+      val identity = Identity(state = random.nextIdentity())
+
+      val value = IdentityId(random.nextInt())
+      assertNotEquals(value, identity.id())
+      identity.setId(value)
+      assertEquals(value, identity.id())
+    }
+  }
+}
diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkConfigTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkConfigTest.kt
new file mode 100644
index 0000000..141beff
--- /dev/null
+++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkConfigTest.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.libquassel.protocol.syncables.common.NetworkConfig
+import de.justjanne.libquassel.protocol.syncables.state.NetworkConfigState
+import de.justjanne.libquassel.protocol.testutil.nextNetworkConfig
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertNotEquals
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import kotlin.random.Random
+
+class NetworkConfigTest {
+  @Test
+  fun testEmpty() {
+    val actual = NetworkConfig().apply {
+      update(emptyMap())
+    }.state()
+
+    assertEquals(NetworkConfigState(), actual)
+  }
+
+  @Test
+  fun testSerialization() {
+    val random = Random(1337)
+    val expected = random.nextNetworkConfig()
+
+    val actual = NetworkConfig().apply {
+      update(NetworkConfig(state = expected).toVariantMap())
+    }.state()
+
+    assertEquals(expected, actual)
+  }
+
+  @Nested
+  inner class Setters {
+    @Test
+    fun testAutoWhoDelay() {
+      val random = Random(1337)
+      val networkConfig = NetworkConfig(state = random.nextNetworkConfig())
+
+      val value = random.nextInt()
+      assertNotEquals(value, networkConfig.autoWhoDelay())
+      networkConfig.setAutoWhoDelay(value)
+      assertEquals(value, networkConfig.autoWhoDelay())
+    }
+
+    @Test
+    fun testAutoWhoEnabled() {
+      val random = Random(1337)
+      val networkConfig = NetworkConfig(state = random.nextNetworkConfig())
+
+      networkConfig.setAutoWhoEnabled(false)
+      assertEquals(false, networkConfig.autoWhoEnabled())
+      networkConfig.setAutoWhoEnabled(true)
+      assertEquals(true, networkConfig.autoWhoEnabled())
+    }
+
+    @Test
+    fun testAutoWhoInterval() {
+      val random = Random(1337)
+      val networkConfig = NetworkConfig(state = random.nextNetworkConfig())
+
+      val value = random.nextInt()
+      assertNotEquals(value, networkConfig.autoWhoInterval())
+      networkConfig.setAutoWhoInterval(value)
+      assertEquals(value, networkConfig.autoWhoInterval())
+    }
+
+    @Test
+    fun testAutoWhoNickLimit() {
+      val random = Random(1337)
+      val networkConfig = NetworkConfig(state = random.nextNetworkConfig())
+
+      val value = random.nextInt()
+      assertNotEquals(value, networkConfig.autoWhoNickLimit())
+      networkConfig.setAutoWhoNickLimit(value)
+      assertEquals(value, networkConfig.autoWhoNickLimit())
+    }
+
+    @Test
+    fun testMaxPingCount() {
+      val random = Random(1337)
+      val networkConfig = NetworkConfig(state = random.nextNetworkConfig())
+
+      val value = random.nextInt()
+      assertNotEquals(value, networkConfig.maxPingCount())
+      networkConfig.setMaxPingCount(value)
+      assertEquals(value, networkConfig.maxPingCount())
+    }
+
+    @Test
+    fun testPingInterval() {
+      val random = Random(1337)
+      val networkConfig = NetworkConfig(state = random.nextNetworkConfig())
+
+      val value = random.nextInt()
+      assertNotEquals(value, networkConfig.pingInterval())
+      networkConfig.setPingInterval(value)
+      assertEquals(value, networkConfig.pingInterval())
+    }
+
+    @Test
+    fun testPingTimeoutEnabled() {
+      val random = Random(1337)
+      val networkConfig = NetworkConfig(state = random.nextNetworkConfig())
+
+      networkConfig.setPingTimeoutEnabled(false)
+      assertEquals(false, networkConfig.pingTimeoutEnabled())
+      networkConfig.setPingTimeoutEnabled(true)
+      assertEquals(true, networkConfig.pingTimeoutEnabled())
+    }
+
+    @Test
+    fun testStandardCtcp() {
+      val random = Random(1337)
+      val networkConfig = NetworkConfig(state = random.nextNetworkConfig())
+
+      networkConfig.setStandardCtcp(false)
+      assertEquals(false, networkConfig.standardCtcp())
+      networkConfig.setStandardCtcp(true)
+      assertEquals(true, networkConfig.standardCtcp())
+    }
+  }
+}
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 5a67903..a8f7820 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
@@ -13,6 +13,7 @@ import de.justjanne.libquassel.protocol.models.ids.IdentityId
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
 import de.justjanne.libquassel.protocol.models.network.ChannelModeType
 import de.justjanne.libquassel.protocol.models.network.ConnectionState
+import de.justjanne.libquassel.protocol.models.network.NetworkInfo
 import de.justjanne.libquassel.protocol.models.network.NetworkServer
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.models.types.QuasselType
@@ -70,12 +71,53 @@ class NetworkTest {
     assertEquals(expected, actual)
   }
 
+  @Test
+  fun testNetworkInfo() {
+    val random = Random(1337)
+    val networkId = NetworkId(random.nextInt())
+    val expected = random.nextNetwork(networkId)
+
+    val actual = Network(state = NetworkState(networkId = networkId)).apply {
+      update(Network(state = expected).toVariantMap())
+    }
+
+    assertEquals(
+      NetworkInfo(
+        networkName = expected.networkName,
+        networkId = expected.networkId,
+        identity = expected.identity,
+        codecForServer = expected.codecForServer,
+        codecForEncoding = expected.codecForEncoding,
+        codecForDecoding = expected.codecForDecoding,
+        serverList = expected.serverList,
+        useRandomServer = expected.useRandomServer,
+        perform = expected.perform,
+        useAutoIdentify = expected.useAutoIdentify,
+        autoIdentifyService = expected.autoIdentifyService,
+        autoIdentifyPassword = expected.autoIdentifyPassword,
+        useSasl = expected.useSasl,
+        saslAccount = expected.saslAccount,
+        saslPassword = expected.saslPassword,
+        useAutoReconnect = expected.useAutoReconnect,
+        autoReconnectInterval = expected.autoReconnectInterval,
+        autoReconnectRetries = expected.autoReconnectRetries,
+        unlimitedReconnectRetries = expected.unlimitedReconnectRetries,
+        rejoinChannels = expected.rejoinChannels,
+        useCustomMessageRate = expected.useCustomMessageRate,
+        messageRateBurstSize = expected.messageRateBurstSize,
+        messageRateDelay = expected.messageRateDelay,
+        unlimitedMessageRate = expected.unlimitedMessageRate
+      ),
+      actual.networkInfo()
+    )
+  }
+
   @Nested
   inner class Setters {
     @Test
     fun testIdentity() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals(IdentityId(4), network.identity())
       network.setIdentity(IdentityId(4))
@@ -85,7 +127,7 @@ class NetworkTest {
     @Test
     fun testMyNick() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals("justJanne", network.myNick())
       network.setMyNick("justJanne")
@@ -95,7 +137,7 @@ class NetworkTest {
     @Test
     fun testLatency() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals(500, network.latency())
       network.setLatency(500)
@@ -105,7 +147,7 @@ class NetworkTest {
     @Test
     fun testNetworkName() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals("Freenode", network.networkName())
       network.setNetworkName("Freenode")
@@ -115,7 +157,7 @@ class NetworkTest {
     @Test
     fun testCurrentServer() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals("irc.freenode.org", network.currentServer())
       network.setCurrentServer("irc.freenode.org")
@@ -125,7 +167,7 @@ class NetworkTest {
     @Test
     fun testConnectionStateValid() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals(ConnectionState.Initializing, network.connectionState())
       network.setConnectionState(ConnectionState.Initializing.value)
@@ -135,7 +177,7 @@ class NetworkTest {
     @Test
     fun testConnectionStateInvalid() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals(ConnectionState.Disconnected, network.connectionState())
       network.setConnectionState(-2)
@@ -145,7 +187,7 @@ class NetworkTest {
     @Test
     fun testServerList() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       val desired = listOf(
         NetworkServer(
@@ -173,7 +215,7 @@ class NetworkTest {
     @Test
     fun testUseRandomServer() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       network.setUseRandomServer(false)
       assertEquals(false, network.useRandomServer())
@@ -184,7 +226,7 @@ class NetworkTest {
     @Test
     fun testPerform() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       val value = listOf(
         "/wait 5; /ns ghost",
@@ -206,7 +248,7 @@ class NetworkTest {
     @Test
     fun testUseAutoIdentify() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       network.setUseAutoIdentify(false)
       assertEquals(false, network.useAutoIdentify())
@@ -217,7 +259,7 @@ class NetworkTest {
     @Test
     fun testAutoIdentifyPassword() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals("hunter2", network.autoIdentifyPassword())
       network.setAutoIdentifyPassword("hunter2")
@@ -227,7 +269,7 @@ class NetworkTest {
     @Test
     fun testAutoIdentifyService() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals("NickServ", network.autoIdentifyService())
       network.setAutoIdentifyService("NickServ")
@@ -237,7 +279,7 @@ class NetworkTest {
     @Test
     fun testUseSasl() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       network.setUseSasl(false)
       assertEquals(false, network.useSasl())
@@ -248,7 +290,7 @@ class NetworkTest {
     @Test
     fun testSaslAccount() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals("AzureDiamond", network.saslAccount())
       network.setSaslAccount("AzureDiamond")
@@ -258,7 +300,7 @@ class NetworkTest {
     @Test
     fun testSaslPassword() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals("hunter2", network.saslPassword())
       network.setSaslPassword("hunter2")
@@ -268,7 +310,7 @@ class NetworkTest {
     @Test
     fun testUseAutoReconnect() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       network.setUseAutoReconnect(false)
       assertEquals(false, network.useAutoReconnect())
@@ -279,7 +321,7 @@ class NetworkTest {
     @Test
     fun testAutoReconnectInterval() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals(2500u, network.autoReconnectInterval())
       network.setAutoReconnectInterval(2500u)
@@ -289,7 +331,7 @@ class NetworkTest {
     @Test
     fun testAutoReconnectRetries() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals(7u.toUShort(), network.autoReconnectRetries())
       network.setAutoReconnectRetries(7u.toUShort())
@@ -299,7 +341,7 @@ class NetworkTest {
     @Test
     fun testUnlimitedReconnectRetries() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       network.setUnlimitedReconnectRetries(false)
       assertEquals(false, network.unlimitedReconnectRetries())
@@ -310,7 +352,7 @@ class NetworkTest {
     @Test
     fun testRejoinChannels() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       network.setRejoinChannels(false)
       assertEquals(false, network.rejoinChannels())
@@ -321,7 +363,7 @@ class NetworkTest {
     @Test
     fun testUseCustomMessageRate() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       network.setUseCustomMessageRate(false)
       assertEquals(false, network.useCustomMessageRate())
@@ -332,7 +374,7 @@ class NetworkTest {
     @Test
     fun testMessageRateBurstSize() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals(20u, network.messageRateBurstSize())
       network.setMessageRateBurstSize(20u)
@@ -342,7 +384,7 @@ class NetworkTest {
     @Test
     fun testMessageRateDelay() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals(1200u, network.messageRateDelay())
       network.setMessageRateDelay(1200u)
@@ -352,7 +394,7 @@ class NetworkTest {
     @Test
     fun testUnlimitedMessageRate() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       network.setUnlimitedMessageRate(false)
       assertEquals(false, network.unlimitedMessageRate())
@@ -363,7 +405,7 @@ class NetworkTest {
     @Test
     fun testCodecForServer() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals("UTF_8", network.codecForServer())
       network.setCodecForServer(StringSerializerUtf8.serializeRaw("UTF_8"))
@@ -375,7 +417,7 @@ class NetworkTest {
     @Test
     fun testCodecForEncoding() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals("UTF_8", network.codecForEncoding())
       network.setCodecForEncoding(StringSerializerUtf8.serializeRaw("UTF_8"))
@@ -387,7 +429,7 @@ class NetworkTest {
     @Test
     fun testCodecForDecoding() {
       val random = Random(1337)
-      val network = Network(state = random.nextNetwork(NetworkId(random.nextInt())))
+      val network = Network(state = random.nextNetwork())
 
       assertNotEquals("UTF_8", network.codecForDecoding())
       network.setCodecForDecoding(StringSerializerUtf8.serializeRaw("UTF_8"))
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 5bc159b..dd0d7ff 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
@@ -10,11 +10,18 @@
 package de.justjanne.libquassel.protocol.testutil
 
 import de.justjanne.bitflags.of
+import de.justjanne.libquassel.protocol.features.FeatureSet
+import de.justjanne.libquassel.protocol.features.LegacyFeature
+import de.justjanne.libquassel.protocol.features.QuasselFeatureName
 import de.justjanne.libquassel.protocol.models.BufferActivity
+import de.justjanne.libquassel.protocol.models.BufferInfo
+import de.justjanne.libquassel.protocol.models.ConnectedClient
 import de.justjanne.libquassel.protocol.models.alias.Alias
 import de.justjanne.libquassel.protocol.models.flags.BufferType
+import de.justjanne.libquassel.protocol.models.flags.MessageType
 import de.justjanne.libquassel.protocol.models.ids.BufferId
 import de.justjanne.libquassel.protocol.models.ids.IdentityId
+import de.justjanne.libquassel.protocol.models.ids.MsgId
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
 import de.justjanne.libquassel.protocol.models.network.NetworkServer
 import de.justjanne.libquassel.protocol.models.rules.HighlightRule
@@ -23,12 +30,20 @@ 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.BufferSyncerState
 import de.justjanne.libquassel.protocol.syncables.state.BufferViewConfigState
 import de.justjanne.libquassel.protocol.syncables.state.BufferViewManagerState
+import de.justjanne.libquassel.protocol.syncables.state.CertManagerState
+import de.justjanne.libquassel.protocol.syncables.state.CoreInfoState
+import de.justjanne.libquassel.protocol.syncables.state.DccConfigState
+import de.justjanne.libquassel.protocol.syncables.state.IdentityState
 import de.justjanne.libquassel.protocol.syncables.state.IrcChannelState
 import de.justjanne.libquassel.protocol.syncables.state.IrcUserState
+import de.justjanne.libquassel.protocol.syncables.state.NetworkConfigState
 import de.justjanne.libquassel.protocol.syncables.state.NetworkState
 import org.threeten.bp.Instant
+import org.threeten.bp.temporal.ChronoUnit
+import java.net.InetAddress
 import java.util.EnumSet
 import java.util.Locale
 import java.util.UUID
@@ -54,7 +69,7 @@ inline fun <reified T : Enum<T>> Random.nextEnum(): T {
 
 fun Random.nextInstant(): Instant = Instant.ofEpochMilli(nextLong())
 
-fun Random.nextNetwork(networkId: NetworkId) = NetworkState(
+fun Random.nextNetwork(networkId: NetworkId = NetworkId(nextInt())) = NetworkState(
   networkId = networkId,
   identity = IdentityId(nextInt()),
   myNick = nextString(),
@@ -195,6 +210,78 @@ fun Random.nextBufferViewManager() = BufferViewManagerState(
   }.associateBy(BufferViewConfig::bufferViewId)
 )
 
+fun Random.nextBufferSyncer(): BufferSyncerState {
+  val bufferInfos = List(nextInt(20)) { nextBufferInfo() }
+  val buffers = bufferInfos.map(BufferInfo::bufferId)
+  return BufferSyncerState(
+    activities = buffers.associateWith {
+      MessageType.of(nextUInt())
+    },
+    highlightCounts = buffers.associateWith { nextUInt(20u).toInt() },
+    lastSeenMsg = buffers.associateWith { MsgId(nextLong()) },
+    markerLines = buffers.associateWith { MsgId(nextLong()) },
+    bufferInfos = bufferInfos.associateBy(BufferInfo::bufferId),
+  )
+}
+
+fun Random.nextBufferInfo(
+  bufferId: BufferId = BufferId(nextInt()),
+  networkId: NetworkId = NetworkId(nextInt())
+) = BufferInfo(
+  bufferId = bufferId,
+  networkId = networkId,
+  type = BufferType.of(nextUInt().toUShort()),
+  groupId = -1,
+  bufferName = nextString()
+)
+
+fun Random.nextCertManager(identityId: IdentityId = IdentityId(nextInt())) = CertManagerState(
+  identityId = identityId,
+  certificatePem = nextString(),
+  privateKeyPem = nextString()
+)
+
+fun Random.nextCoreInfo() = CoreInfoState(
+  version = nextString(),
+  versionDate = nextInstant().truncatedTo(ChronoUnit.SECONDS),
+  startTime = nextInstant(),
+  connectedClientCount = nextInt(),
+  connectedClients = List(nextInt(20)) {
+    nextConnectedClient()
+  }
+)
+
+fun Random.nextConnectedClient() = ConnectedClient(
+  id = nextInt(),
+  remoteAddress = nextString(),
+  location = nextString(),
+  version = nextString(),
+  versionDate = nextInstant().truncatedTo(ChronoUnit.SECONDS),
+  connectedSince = nextInstant(),
+  secure = nextBoolean(),
+  features = nextFeatureSet()
+)
+
+fun Random.nextFeatureSet() = FeatureSet.build(
+  LegacyFeature.of(nextUInt()),
+  List(nextInt(20)) {
+    QuasselFeatureName(nextString())
+  }
+)
+
+fun Random.nextDccConfig() = DccConfigState(
+  dccEnabled = nextBoolean(),
+  outgoingIp = InetAddress.getByAddress(nextBytes(4)),
+  ipDetectionMode = nextEnum(),
+  portSelectionMode = nextEnum(),
+  minPort = nextUInt().toUShort(),
+  maxPort = nextUInt().toUShort(),
+  chunkSize = nextInt(),
+  sendTimeout = nextInt(),
+  usePassiveDcc = nextBoolean(),
+  useFastSend = nextBoolean()
+)
+
 fun Random.nextHighlightRule(id: Int) = HighlightRule(
   id = id,
   content = nextString(),
@@ -206,6 +293,32 @@ fun Random.nextHighlightRule(id: Int) = HighlightRule(
   channel = nextString()
 )
 
+fun Random.nextIdentity(
+  identityId: IdentityId = IdentityId(nextInt())
+) = IdentityState(
+  identityId = identityId,
+  identityName = nextString(),
+  realName = nextString(),
+  nicks = List(nextInt(20)) {
+    nextString()
+  },
+  awayNick = nextString(),
+  awayNickEnabled = nextBoolean(),
+  awayReason = nextString(),
+  awayReasonEnabled = nextBoolean(),
+  autoAwayEnabled = nextBoolean(),
+  autoAwayTime = nextInt(),
+  autoAwayReason = nextString(),
+  autoAwayReasonEnabled = nextBoolean(),
+  detachAwayEnabled = nextBoolean(),
+  detachAwayReason = nextString(),
+  detachAwayReasonEnabled = nextBoolean(),
+  ident = nextString(),
+  kickReason = nextString(),
+  partReason = nextString(),
+  quitReason = nextString()
+)
+
 fun Random.nextIgnoreRule() = IgnoreRule(
   type = nextEnum(),
   ignoreRule = nextString(),
@@ -215,3 +328,14 @@ fun Random.nextIgnoreRule() = IgnoreRule(
   scope = nextEnum(),
   scopeRule = nextString()
 )
+
+fun Random.nextNetworkConfig() = NetworkConfigState(
+  pingTimeoutEnabled = nextBoolean(),
+  pingInterval = nextInt(),
+  maxPingCount = nextInt(),
+  autoWhoEnabled = nextBoolean(),
+  autoWhoInterval = nextInt(),
+  autoWhoNickLimit = nextInt(),
+  autoWhoDelay = nextInt(),
+  standardCtcp = nextBoolean()
+)
-- 
GitLab