Skip to content
Snippets Groups Projects
Verified Commit 842dd088 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Implement end-to-end tests with testcontainers

parent 0ab64730
Branches
No related tags found
No related merge requests found
Pipeline #584 failed
Showing
with 367 additions and 143 deletions
image: "k8r.eu/justjanne/quasseldroid-build-env:30-30.0.3"
image: "k8r.eu/justjanne/quasseldroid-build-env:1eb7fa8"
cache:
key: "$CI_PROJECT_NAME"
......@@ -31,6 +31,7 @@ build:
test:
stage: "test"
script:
- "dockerd-rootless.sh"
- "./gradlew check -x connectedCheck coberturaTestReport"
artifacts:
paths:
......
package de.kuschku.quasseldroid
import de.kuschku.bitflags.of
import de.kuschku.libquassel.protocol.connection.*
import de.kuschku.libquassel.protocol.features.FeatureSet
import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
import de.kuschku.libquassel.protocol.io.contentToString
import de.kuschku.libquassel.protocol.messages.handshake.ClientInit
import de.kuschku.libquassel.protocol.serializers.HandshakeSerializers
import de.kuschku.libquassel.protocol.serializers.handshake.ClientInitAckSerializer
import de.kuschku.libquassel.protocol.serializers.handshake.ClientInitRejectSerializer
import de.kuschku.libquassel.protocol.serializers.handshake.ClientInitSerializer
import de.kuschku.libquassel.protocol.serializers.primitive.HandshakeMapSerializer
import de.kuschku.libquassel.protocol.serializers.primitive.IntSerializer
import de.kuschku.libquassel.protocol.serializers.primitive.UIntSerializer
import de.kuschku.libquassel.protocol.variant.into
import de.kuschku.quasseldroid.protocol.io.CoroutineChannel
import kotlinx.coroutines.runBlocking
import org.junit.Test
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.X509TrustManager
class ExampleUnitTest {
@Test
fun testNetworking() {
val context = SSLContext.getInstance("TLSv1.3")
context.init(null, arrayOf(object : X509TrustManager {
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
// FIXME: accept everything
}
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
// FIXME: accept everything
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
// FIXME: accept nothing
return emptyArray()
}
}), null)
runBlocking {
val connectionFeatureSet = FeatureSet.all()
val sizeBuffer = ByteBuffer.allocateDirect(4)
val sendBuffer = ChainedByteBuffer(direct = true)
val channel = CoroutineChannel()
channel.connect(InetSocketAddress("kuschku.de", 4242))
suspend fun readAmount(amount: Int? = null): Int {
if (amount != null) return amount
sizeBuffer.clear()
channel.read(sizeBuffer)
sizeBuffer.flip()
val size = IntSerializer.deserialize(sizeBuffer, connectionFeatureSet)
sizeBuffer.clear()
return size
}
suspend fun write(sizePrefix: Boolean = true, f: suspend (ChainedByteBuffer) -> Unit) {
f(sendBuffer)
if (sizePrefix) {
sizeBuffer.clear()
sizeBuffer.putInt(sendBuffer.size)
sizeBuffer.flip()
channel.write(sizeBuffer)
sizeBuffer.clear()
}
channel.write(sendBuffer)
channel.flush()
sendBuffer.clear()
}
suspend fun <T> read(amount: Int? = null, f: suspend (ByteBuffer) -> T): T {
val amount1 = readAmount(amount)
val messageBuffer = ByteBuffer.allocateDirect(minOf(amount1, 65 * 1024 * 1024))
channel.read(messageBuffer)
messageBuffer.flip()
return f(messageBuffer)
}
println("Writing protocol")
write(sizePrefix = false) {
ConnectionHeaderSerializer.serialize(
it,
ConnectionHeader(
features = ProtocolFeature.of(
ProtocolFeature.Compression,
ProtocolFeature.TLS
),
versions = listOf(
ProtocolMeta(
0x0000u,
ProtocolVersion.Datastream,
),
)
),
connectionFeatureSet
)
}
println("Reading protocol")
read(4) {
println(ProtocolInfoSerializer.deserialize(it, connectionFeatureSet))
println(channel.tlsInfo.value)
channel.enableTLS(context)
println(channel.tlsInfo.value)
channel.enableCompression()
}
println("Writing clientInit")
write {
HandshakeMapSerializer.serialize(
it,
ClientInitSerializer.serialize(ClientInit(
clientVersion = "Quasseldroid test",
buildDate = "Never",
clientFeatures = connectionFeatureSet.legacyFeatures(),
featureList = connectionFeatureSet.featureList()
)),
connectionFeatureSet
)
}
read {
val data = HandshakeMapSerializer.deserialize(it, connectionFeatureSet)
println(data)
val msgType: String = data["MsgType"].into("")
val message: Any? = HandshakeSerializers[msgType]?.deserialize(data)
println(message)
}
}
}
}
......@@ -43,6 +43,7 @@ allprojects {
extra["junit4Version"] = "4.13.1"
extra["junit5Version"] = "5.3.1"
extra["mdcVersion"] = "1.2.1"
extra["testContainersVersion"] = "1.15.1"
repositories {
google()
......
plugins {
kotlin("jvm")
id("jacoco")
id("de.kuschku.coverageconverter")
}
tasks.withType<Test> {
useJUnitPlatform()
}
tasks.getByName<JacocoReport>("jacocoTestReport") {
reports {
sourceDirectories.from(fileTree("src/main/kotlin"))
classDirectories.from(fileTree("build/classes"))
xml.destination = File("$buildDir/reports/jacoco/report.xml")
html.isEnabled = true
xml.isEnabled = true
csv.isEnabled = false
}
}
dependencies {
implementation(kotlin("stdlib"))
implementation("org.jetbrains.kotlinx", "kotlinx-coroutines-core", "1.4.2")
implementation("org.jetbrains.kotlinx", "kotlinx-coroutines-test", "1.4.2")
api(project(":protocol"))
val junit5Version: String by project.extra
testImplementation("org.junit.jupiter", "junit-jupiter-api", junit5Version)
testImplementation("org.junit.jupiter", "junit-jupiter-params", junit5Version)
testRuntimeOnly("org.junit.jupiter", "junit-jupiter-engine", junit5Version)
val hamcrestVersion: String by project.extra
testImplementation("org.hamcrest", "hamcrest-library", hamcrestVersion)
val testContainersVersion: String by project.extra
testImplementation("org.testcontainers", "testcontainers", testContainersVersion)
testImplementation("org.testcontainers", "junit-jupiter", testContainersVersion)
testImplementation("org.slf4j", "slf4j-simple", "1.7.30")
}
......@@ -17,9 +17,8 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.kuschku.quasseldroid.protocol.io
package de.kuschku.libquassel.io
import android.util.Log
import java.io.InputStream
import java.nio.ByteBuffer
import java.nio.channels.ReadableByteChannel
......@@ -61,7 +60,7 @@ class ReadableWrappedChannel(
}
if (readData <= 0) {
Log.e("ReadableWrappedChannel", "Read: $readData")
error("[ReadableWrappedChannel] Read: $readData")
}
// read is negative if no data was read, in that case, terminate
......
......@@ -19,6 +19,7 @@
package de.kuschku.quasseldroid.protocol.io
import de.kuschku.libquassel.io.ReadableWrappedChannel
import de.kuschku.quasseldroid.util.TlsInfo
import java.io.Flushable
import java.io.InputStream
......
/*
* Quasseldroid - Quassel client for Android
*
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.kuschku.libquassel
import de.kuschku.bitflags.of
import de.kuschku.libquassel.protocol.connection.*
import de.kuschku.libquassel.protocol.features.FeatureSet
import de.kuschku.libquassel.protocol.io.ChainedByteBuffer
import de.kuschku.libquassel.protocol.messages.handshake.ClientInit
import de.kuschku.libquassel.protocol.serializers.HandshakeSerializers
import de.kuschku.libquassel.protocol.serializers.handshake.ClientInitSerializer
import de.kuschku.libquassel.protocol.serializers.primitive.HandshakeMapSerializer
import de.kuschku.libquassel.protocol.serializers.primitive.IntSerializer
import de.kuschku.libquassel.protocol.variant.into
import de.kuschku.quasseldroid.protocol.io.CoroutineChannel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.slf4j.LoggerFactory
import org.testcontainers.containers.BindMode
import org.testcontainers.containers.output.Slf4jLogConsumer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import org.testcontainers.utility.MountableFile
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import javax.net.ssl.SSLContext
@ExperimentalCoroutinesApi
@Testcontainers
class EndToEndTest {
@Container
val quassel = QuasselContainer()
.withExposedPorts(4242)
.withClasspathResourceMapping("/quasseltest.crt", "/quasseltest.crt", BindMode.READ_WRITE)
.withEnv("SSL_CERT_FILE", "/quasseltest.crt")
.withClasspathResourceMapping("/quasseltest.key", "/quasseltest.key", BindMode.READ_WRITE)
.withEnv("SSL_KEY_FILE", "/quasseltest.key")
.withEnv("CONFIG_FROM_ENVIRONMENT", "true")
.withEnv("DB_BACKEND", "SQLite")
.withEnv("AUTH_AUTHENTICATOR", "Database")
private val sslContext = SSLContext.getInstance("TLSv1.3").apply {
init(null, arrayOf(TestX509TrustManager), null)
}
private val connectionFeatureSet = FeatureSet.all()
private val sizeBuffer = ByteBuffer.allocateDirect(4)
private val sendBuffer = ChainedByteBuffer(direct = true)
private val channel = CoroutineChannel()
@BeforeEach
fun setUp() {
quassel.followOutput(Slf4jLogConsumer(LoggerFactory.getLogger(EndToEndTest::class.java)))
}
@Test
fun testConnect() = runBlocking {
channel.connect(InetSocketAddress(
quassel.host,
quassel.getMappedPort(4242)
))
println("Writing protocol")
write(sizePrefix = false) {
ConnectionHeaderSerializer.serialize(
it,
ConnectionHeader(
features = ProtocolFeature.of(
ProtocolFeature.Compression,
ProtocolFeature.TLS
),
versions = listOf(
ProtocolMeta(
0x0000u,
ProtocolVersion.Datastream,
),
)
),
connectionFeatureSet
)
}
println("Reading protocol")
read(4) {
val protocol = ProtocolInfoSerializer.deserialize(it, connectionFeatureSet)
println(protocol)
if (protocol.flags.contains(ProtocolFeature.TLS)) {
channel.enableTLS(sslContext)
}
if (protocol.flags.contains(ProtocolFeature.Compression)) {
channel.enableCompression()
}
}
println("Writing clientInit")
write {
HandshakeMapSerializer.serialize(
it,
ClientInitSerializer.serialize(ClientInit(
clientVersion = "Quasseldroid test",
buildDate = "Never",
clientFeatures = connectionFeatureSet.legacyFeatures(),
featureList = connectionFeatureSet.featureList()
)),
connectionFeatureSet
)
}
read {
val data = HandshakeMapSerializer.deserialize(it, connectionFeatureSet)
println(data)
val msgType: String = data["MsgType"].into("")
val message: Any? = HandshakeSerializers[msgType]?.deserialize(data)
println(message)
}
}
suspend fun readAmount(amount: Int? = null): Int {
if (amount != null) return amount
sizeBuffer.clear()
channel.read(sizeBuffer)
sizeBuffer.flip()
val size = IntSerializer.deserialize(sizeBuffer, connectionFeatureSet)
sizeBuffer.clear()
return size
}
suspend fun write(sizePrefix: Boolean = true, f: suspend (ChainedByteBuffer) -> Unit) {
f(sendBuffer)
if (sizePrefix) {
sizeBuffer.clear()
sizeBuffer.putInt(sendBuffer.size)
sizeBuffer.flip()
channel.write(sizeBuffer)
sizeBuffer.clear()
}
channel.write(sendBuffer)
channel.flush()
sendBuffer.clear()
}
suspend fun <T> read(amount: Int? = null, f: suspend (ByteBuffer) -> T): T {
val amount1 = readAmount(amount)
val messageBuffer = ByteBuffer.allocateDirect(minOf(amount1, 65 * 1024 * 1024))
channel.read(messageBuffer)
messageBuffer.flip()
return f(messageBuffer)
}
}
/*
* Quasseldroid - Quassel client for Android
*
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.kuschku.libquassel
import org.testcontainers.containers.GenericContainer
import org.testcontainers.utility.DockerImageName
class QuasselContainer : GenericContainer<QuasselContainer>(
DockerImageName.parse("k8r.eu/justjanne/quassel-docker:latest")
)
/*
* Quasseldroid - Quassel client for Android
*
* Copyright (c) 2021 Janne Mareike Koschinski
* Copyright (c) 2021 The Quassel Project
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.kuschku.libquassel
import java.security.cert.X509Certificate
import javax.net.ssl.X509TrustManager
object TestX509TrustManager : X509TrustManager {
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
// FIXME: accept everything
}
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
// FIXME: accept everything
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
// FIXME: accept nothing
return emptyArray()
}
}
-----BEGIN CERTIFICATE-----
MIIFHzCCAwegAwIBAgIUEV/Rf5DceQhjul2yqHDa/ODr5+YwDQYJKoZIhvcNAQEL
BQAwHjEcMBoGA1UEAwwTcXVhc3NlbHRlc3QuaW52YWxpZDAgFw0yMTAyMDgyMzM5
MjRaGA8yMTIxMDExNTIzMzkyNFowHjEcMBoGA1UEAwwTcXVhc3NlbHRlc3QuaW52
YWxpZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAM2OZTae4BKzppB2
nEF2e2oGLl1YGaCfqaVeQjKNS3DY8fjIG9lb6hDzE1MneoGtou7TafWvqh2u1Q8p
aRNpcW+ftTqcUecGPuMTQHHSWjZJDocvH2vG5xM48u5DqLNUThs7eEoPis7AGpus
7xm8wmjGokKYZb6WdXGLcOEPLb2ZSQH4MRHkAMtM1zF3/L0bYzRLil9+aQ6y0P3/
4hRfUiy5ZkIiPS3+fJPOj/ZaABukLtffWicbeAvNDk7Jp2FvoVfXPXU14CXQaV16
BhfCbWHamUM4d0ZWu85J8Jbon+JRfmIvg+0z4nlZaJ6a23nkO4ljL6IKdwlEUYbM
uZ4bimCxmk2g3t27DxDBYAeudCQYQjvlA7fYf9b2tBjdvVx7BRyFk09Z+zSWKXVx
67iL5nURY8iOY+sHF7t/D/vbDx1/nDkXZPxEl1jZlDnu2xCkM8sYgocXsfjIaW3f
1dcGJ6lr50g69LgTBBuT2Db3Li91nAr3s+V5Urf1CcTMFipNAdeY3qN717LzKsJS
FpjAtvUJQhPin9Is0AZkYCrDxIYZJYgQZaeDCrXRQwoS8qIhXWbgHXRPUTwgGzmS
og62+rfwn3JDRDKOGuiYIc8uhBj0mOOV/GBegFg1cskaX+EMvdurx2jyNc6y1JOG
WJHZ2ctMSHlkm4/vG9lEHoW43HcPAgMBAAGjUzBRMB0GA1UdDgQWBBQ4uKrImjUv
C/TJhb03I3YJEjgjrjAfBgNVHSMEGDAWgBQ4uKrImjUvC/TJhb03I3YJEjgjrjAP
BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCMahqY1LVdSny9oc7B
AGET5Y/sTdALZN/GslYWjY9iTNx4kXz6u0654onDTBHZ6c+8+dmZdWFrdPTzJuJX
dB4HqQyY2tLhR3N44F6LRkx97klbvSscMJr3QK4344AtksbEq1BGeDCm5VeaZZJU
A+WSNXjJC22EFR5RLftdArRquJVcwCDPWI1lf/iv/iXJPpv8Mq5qFpY1Agusi/sv
PLO6GVV2qPADoI0NjgYCjJ2VVJQeHasFCC+izihmrTQUMc4G/JF9gHMPOLRofm32
zMQ8LK+iWa9p+VoRbdnsWDw4Fy9s34VXuT5ijAz6KbQ8v9alQv6VfYwnZCrylyKH
K3vWg7g1tlyMZP7ExzHfu428lVmYZbf1k7GUA+2Qu9s/jPfszwQzdDnr5dN9jCUj
CIqLXoHZeyIc2Cxu1KMaZvkPVn2/zJUa3RNLfcl+1pGOCwaUd5lcGtdzrk/9lZwR
zHh9nvQWPAE2r36E7CtOgy0uMIfFdNnDc1iYKU2I9075k+bo7y4zvO9gQx3dmkxU
SCm2sKQzkScSBPtCBtsfKfN4csL5FyLevl7C5izpQqtChqLZu9XXDNVTE7jd48Wy
VrIlBGHwGkksmYUqALI81bvBaOZ1GytAIMbRNQ8YRLIPvjuL/CorfqrR5kEKXqsd
eZw3w5qGASg6Xk0d9OxKyjHKeQ==
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
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
jwfYNzNlv1pDyVGAaI8oMDXRMaZ3
-----END PRIVATE KEY-----
......@@ -24,13 +24,17 @@ tasks.getByName<JacocoReport>("jacocoTestReport") {
dependencies {
implementation(kotlin("stdlib"))
implementation("org.threeten", "threetenbp", "1.4.0")
api("org.threeten", "threetenbp", "1.4.0")
api(project(":bitflags"))
api(project(":coverage-annotations"))
val junit5Version: String by project.extra
testImplementation("org.junit.jupiter", "junit-jupiter-api", junit5Version)
testImplementation("org.junit.jupiter", "junit-jupiter-params", junit5Version)
testRuntimeOnly("org.junit.jupiter", "junit-jupiter-engine", junit5Version)
val hamcrestVersion: String by project.extra
testImplementation("org.hamcrest", "hamcrest-library", hamcrestVersion)
val testContainersVersion: String by project.extra
testImplementation("org.testcontainers", "testcontainers", testContainersVersion)
testImplementation("org.testcontainers", "junit-jupiter", testContainersVersion)
}
......@@ -24,5 +24,6 @@ include(
":app",
":bitflags",
":protocol",
":coverage-annotations"
":coverage-annotations",
":libquassel"
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment