Add unit test on RustLinkDesktopHandler
Add unit test on RustLinkMobileHandler Add unit test on DefaultLinkNewDeviceEntryPoint
This commit is contained in:
parent
86cafb7c05
commit
ec10a0bf87
10 changed files with 348 additions and 2 deletions
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.linknewdevice.impl
|
||||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.linknewdevice.api.LinkNewDeviceEntryPoint
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.node.TestParentNode
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultLinkNewDeviceEntryPointTest {
|
||||
@get:Rule
|
||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
@get:Rule
|
||||
val mainDispatcherRule = MainDispatcherRule()
|
||||
|
||||
@Test
|
||||
fun `test node creation`() = runTest {
|
||||
val entryPoint = DefaultLinkNewDeviceEntryPoint()
|
||||
val client = FakeMatrixClient()
|
||||
val parentNode = TestParentNode.create { buildContext, plugins ->
|
||||
LinkNewDeviceFlowNode(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins,
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
linkNewMobileHandler = LinkNewMobileHandler(client),
|
||||
linkNewDesktopHandler = LinkNewDesktopHandler(client),
|
||||
)
|
||||
}
|
||||
val callback: LinkNewDeviceEntryPoint.Callback = object : LinkNewDeviceEntryPoint.Callback {
|
||||
override fun onDone() = lambdaError()
|
||||
}
|
||||
val result = entryPoint.createNode(parentNode, BuildContext.root(null), callback)
|
||||
assertThat(result).isInstanceOf(LinkNewDeviceFlowNode::class.java)
|
||||
}
|
||||
}
|
||||
|
|
@ -49,6 +49,7 @@ import io.element.android.libraries.matrix.impl.encryption.RustEncryptionService
|
|||
import io.element.android.libraries.matrix.impl.exception.mapClientException
|
||||
import io.element.android.libraries.matrix.impl.linknewdevice.RustLinkDesktopHandler
|
||||
import io.element.android.libraries.matrix.impl.linknewdevice.RustLinkMobileHandler
|
||||
import io.element.android.libraries.matrix.impl.linknewdevice.RustQrCodeDataParser
|
||||
import io.element.android.libraries.matrix.impl.mapper.map
|
||||
import io.element.android.libraries.matrix.impl.media.RustMediaLoader
|
||||
import io.element.android.libraries.matrix.impl.media.RustMediaPreviewService
|
||||
|
|
@ -754,6 +755,7 @@ class RustMatrixClient(
|
|||
inner = handler,
|
||||
sessionCoroutineScope = sessionCoroutineScope,
|
||||
sessionDispatcher = sessionDispatcher,
|
||||
qrCodeDataParser = RustQrCodeDataParser(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.linknewdevice
|
||||
|
||||
import org.matrix.rustcomponents.sdk.QrCodeData
|
||||
|
||||
interface QrCodeDataParser {
|
||||
fun parse(data: ByteArray): QrCodeData
|
||||
}
|
||||
|
||||
class RustQrCodeDataParser : QrCodeDataParser {
|
||||
override fun parse(data: ByteArray): QrCodeData {
|
||||
return QrCodeData.fromBytes(data)
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,6 @@ import org.matrix.rustcomponents.sdk.GrantLoginWithQrCodeHandler
|
|||
import org.matrix.rustcomponents.sdk.GrantQrLoginProgress
|
||||
import org.matrix.rustcomponents.sdk.GrantQrLoginProgressListener
|
||||
import org.matrix.rustcomponents.sdk.HumanQrGrantLoginException
|
||||
import org.matrix.rustcomponents.sdk.QrCodeData
|
||||
import org.matrix.rustcomponents.sdk.QrCodeDecodeException
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -33,6 +32,7 @@ class RustLinkDesktopHandler(
|
|||
private val inner: GrantLoginWithQrCodeHandler,
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
private val sessionDispatcher: CoroutineDispatcher,
|
||||
private val qrCodeDataParser: QrCodeDataParser,
|
||||
) : LinkDesktopHandler {
|
||||
private val _linkDesktopStep = MutableStateFlow<LinkDesktopStep>(LinkDesktopStep.Uninitialized)
|
||||
override val linkDesktopStep: StateFlow<LinkDesktopStep> = _linkDesktopStep.asStateFlow()
|
||||
|
|
@ -41,7 +41,7 @@ class RustLinkDesktopHandler(
|
|||
Timber.tag(tag.value).d("Emit Uninitialized")
|
||||
_linkDesktopStep.emit(LinkDesktopStep.Uninitialized)
|
||||
try {
|
||||
val qrCodeData = QrCodeData.fromBytes(data)
|
||||
val qrCodeData = qrCodeDataParser.parse(data)
|
||||
inner.scan(
|
||||
qrCodeData = qrCodeData,
|
||||
progressListener = object : GrantQrLoginProgressListener {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.fixtures.fakes
|
||||
|
||||
import org.matrix.rustcomponents.sdk.CheckCodeSender
|
||||
import org.matrix.rustcomponents.sdk.NoHandle
|
||||
|
||||
class FakeFfiCheckCodeSender : CheckCodeSender(NoHandle)
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.fixtures.fakes
|
||||
|
||||
import org.matrix.rustcomponents.sdk.GrantGeneratedQrLoginProgress
|
||||
import org.matrix.rustcomponents.sdk.GrantGeneratedQrLoginProgressListener
|
||||
import org.matrix.rustcomponents.sdk.GrantLoginWithQrCodeHandler
|
||||
import org.matrix.rustcomponents.sdk.GrantQrLoginProgress
|
||||
import org.matrix.rustcomponents.sdk.GrantQrLoginProgressListener
|
||||
import org.matrix.rustcomponents.sdk.NoHandle
|
||||
import org.matrix.rustcomponents.sdk.QrCodeData
|
||||
|
||||
class FakeFfiGrantLoginWithQrCodeHandler(
|
||||
private val generateResult: () -> Unit = {},
|
||||
private val scanResult: (QrCodeData) -> Unit = {},
|
||||
) : GrantLoginWithQrCodeHandler(NoHandle) {
|
||||
private var generateProgressListener: GrantGeneratedQrLoginProgressListener? = null
|
||||
private var scanProgressListener: GrantQrLoginProgressListener? = null
|
||||
override suspend fun generate(progressListener: GrantGeneratedQrLoginProgressListener) {
|
||||
generateProgressListener = progressListener
|
||||
generateResult()
|
||||
}
|
||||
|
||||
fun emitGenerateProgress(progress: GrantGeneratedQrLoginProgress) {
|
||||
generateProgressListener?.onUpdate(progress)
|
||||
}
|
||||
|
||||
override suspend fun scan(qrCodeData: QrCodeData, progressListener: GrantQrLoginProgressListener) {
|
||||
scanProgressListener = progressListener
|
||||
scanResult(qrCodeData)
|
||||
}
|
||||
|
||||
fun emitScanProgress(progress: GrantQrLoginProgress) {
|
||||
scanProgressListener?.onUpdate(progress)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,8 +14,13 @@ import org.matrix.rustcomponents.sdk.QrCodeData
|
|||
|
||||
class FakeFfiQrCodeData(
|
||||
private val serverNameResult: () -> String? = { lambdaError() },
|
||||
private val toBytesResult: () -> ByteArray = { lambdaError() },
|
||||
) : QrCodeData(NoHandle) {
|
||||
override fun serverName(): String? {
|
||||
return serverNameResult()
|
||||
}
|
||||
|
||||
override fun toBytes(): ByteArray {
|
||||
return toBytesResult()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.linknewdevice
|
||||
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiQrCodeData
|
||||
import org.matrix.rustcomponents.sdk.QrCodeData
|
||||
|
||||
class FakeQrCodeDataParser : QrCodeDataParser {
|
||||
override fun parse(data: ByteArray): QrCodeData {
|
||||
return FakeFfiQrCodeData()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package io.element.android.libraries.matrix.impl.linknewdevice
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.linknewdevice.ErrorType
|
||||
import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopStep
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiGrantLoginWithQrCodeHandler
|
||||
import io.element.android.libraries.matrix.test.QR_CODE_DATA
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.matrix.rustcomponents.sdk.GrantQrLoginProgress
|
||||
import org.matrix.rustcomponents.sdk.HumanQrGrantLoginException
|
||||
import org.matrix.rustcomponents.sdk.QrCodeDecodeException
|
||||
|
||||
class RustLinkDesktopHandlerTest {
|
||||
@Test
|
||||
fun `handleScannedQrCode function works as expected`() = runTest {
|
||||
val handler = FakeFfiGrantLoginWithQrCodeHandler()
|
||||
val sut = createRustLinkDesktopHandler(
|
||||
handler,
|
||||
)
|
||||
sut.linkDesktopStep.test {
|
||||
val initialItem = awaitItem()
|
||||
assertThat(initialItem).isEqualTo(LinkDesktopStep.Uninitialized)
|
||||
backgroundScope.launch {
|
||||
sut.handleScannedQrCode(QR_CODE_DATA)
|
||||
}
|
||||
runCurrent()
|
||||
// progress from the handler is mapped and emitted
|
||||
listOf(
|
||||
GrantQrLoginProgress.Starting to LinkDesktopStep.Starting,
|
||||
GrantQrLoginProgress.SyncingSecrets to LinkDesktopStep.SyncingSecrets,
|
||||
GrantQrLoginProgress.WaitingForAuth("aVerificationUri")
|
||||
to LinkDesktopStep.WaitingForAuth("aVerificationUri"),
|
||||
GrantQrLoginProgress.EstablishingSecureChannel(1.toUByte(), "1")
|
||||
to LinkDesktopStep.EstablishingSecureChannel(1.toUByte(), "1"),
|
||||
GrantQrLoginProgress.Done to LinkDesktopStep.Done,
|
||||
).forEach { (progress, expectedStep) ->
|
||||
handler.emitScanProgress(progress)
|
||||
assertThat(awaitItem()).isEqualTo(expectedStep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when handleScannedQrCode throws QrCodeDecodeException, the handler emits error step`() = runTest {
|
||||
val handler = FakeFfiGrantLoginWithQrCodeHandler(
|
||||
scanResult = { throw QrCodeDecodeException.Crypto("Scan failed") }
|
||||
)
|
||||
val sut = createRustLinkDesktopHandler(
|
||||
handler,
|
||||
)
|
||||
sut.linkDesktopStep.test {
|
||||
val initialItem = awaitItem()
|
||||
assertThat(initialItem).isEqualTo(LinkDesktopStep.Uninitialized)
|
||||
backgroundScope.launch {
|
||||
sut.handleScannedQrCode(QR_CODE_DATA)
|
||||
}
|
||||
runCurrent()
|
||||
val errorState = awaitItem()
|
||||
assertThat(errorState).isInstanceOf(LinkDesktopStep.InvalidQrCode::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when handleScannedQrCode throws HumanQrGrantLoginException, the handler emits error step`() = runTest {
|
||||
val handler = FakeFfiGrantLoginWithQrCodeHandler(
|
||||
scanResult = { throw HumanQrGrantLoginException.InvalidCheckCode("Invalid check code") }
|
||||
)
|
||||
val sut = createRustLinkDesktopHandler(
|
||||
handler,
|
||||
)
|
||||
sut.linkDesktopStep.test {
|
||||
val initialItem = awaitItem()
|
||||
assertThat(initialItem).isEqualTo(LinkDesktopStep.Uninitialized)
|
||||
backgroundScope.launch {
|
||||
sut.handleScannedQrCode(QR_CODE_DATA)
|
||||
}
|
||||
runCurrent()
|
||||
val errorState = awaitItem()
|
||||
assertThat(errorState).isInstanceOf(LinkDesktopStep.Error::class.java)
|
||||
val errorType = (errorState as LinkDesktopStep.Error).errorType
|
||||
assertThat(errorType).isInstanceOf(ErrorType.InvalidCheckCode::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createRustLinkDesktopHandler(
|
||||
handler: FakeFfiGrantLoginWithQrCodeHandler = FakeFfiGrantLoginWithQrCodeHandler(),
|
||||
) = RustLinkDesktopHandler(
|
||||
inner = handler,
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
sessionDispatcher = StandardTestDispatcher(testScheduler),
|
||||
qrCodeDataParser = FakeQrCodeDataParser(),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package io.element.android.libraries.matrix.impl.linknewdevice
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.linknewdevice.ErrorType
|
||||
import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileStep
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiCheckCodeSender
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiGrantLoginWithQrCodeHandler
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiQrCodeData
|
||||
import io.element.android.libraries.matrix.test.QR_CODE_DATA_RECIPROCATE
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.matrix.rustcomponents.sdk.GrantGeneratedQrLoginProgress
|
||||
import org.matrix.rustcomponents.sdk.HumanQrGrantLoginException
|
||||
|
||||
class RustLinkMobileHandlerTest {
|
||||
@Test
|
||||
fun `start function works as expected`() = runTest {
|
||||
val handler = FakeFfiGrantLoginWithQrCodeHandler()
|
||||
val sut = createRustLinkMobileHandler(
|
||||
handler,
|
||||
)
|
||||
sut.linkMobileStep.test {
|
||||
val initialItem = awaitItem()
|
||||
assertThat(initialItem).isEqualTo(LinkMobileStep.Uninitialized)
|
||||
backgroundScope.launch {
|
||||
sut.start()
|
||||
}
|
||||
runCurrent()
|
||||
// progress from the handler is mapped and emitted
|
||||
listOf(
|
||||
GrantGeneratedQrLoginProgress.Starting to LinkMobileStep.Starting::class.java,
|
||||
GrantGeneratedQrLoginProgress.SyncingSecrets to LinkMobileStep.SyncingSecrets::class.java,
|
||||
GrantGeneratedQrLoginProgress.WaitingForAuth("aVerificationUri")
|
||||
to LinkMobileStep.WaitingForAuth::class.java,
|
||||
GrantGeneratedQrLoginProgress.QrScanned(FakeFfiCheckCodeSender())
|
||||
to LinkMobileStep.QrScanned::class.java,
|
||||
GrantGeneratedQrLoginProgress.QrReady(FakeFfiQrCodeData(toBytesResult = { QR_CODE_DATA_RECIPROCATE }))
|
||||
to LinkMobileStep.QrReady::class.java,
|
||||
GrantGeneratedQrLoginProgress.Done to LinkMobileStep.Done::class.java,
|
||||
).forEach { (progress, expectedStepClass) ->
|
||||
handler.emitGenerateProgress(progress)
|
||||
assertThat(awaitItem()).isInstanceOf(expectedStepClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when start throws HumanQrGrantLoginException, the handler emits error step`() = runTest {
|
||||
val handler = FakeFfiGrantLoginWithQrCodeHandler(
|
||||
generateResult = { throw HumanQrGrantLoginException.NotFound("Timeout") }
|
||||
)
|
||||
val sut = createRustLinkMobileHandler(
|
||||
handler,
|
||||
)
|
||||
sut.linkMobileStep.test {
|
||||
val initialItem = awaitItem()
|
||||
assertThat(initialItem).isEqualTo(LinkMobileStep.Uninitialized)
|
||||
backgroundScope.launch {
|
||||
sut.start()
|
||||
}
|
||||
runCurrent()
|
||||
val errorState = awaitItem()
|
||||
assertThat(errorState).isInstanceOf(LinkMobileStep.Error::class.java)
|
||||
val errorType = (errorState as LinkMobileStep.Error).errorType
|
||||
assertThat(errorType).isInstanceOf(ErrorType.NotFound::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createRustLinkMobileHandler(
|
||||
handler: FakeFfiGrantLoginWithQrCodeHandler = FakeFfiGrantLoginWithQrCodeHandler(),
|
||||
) = RustLinkMobileHandler(
|
||||
inner = handler,
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
sessionDispatcher = StandardTestDispatcher(testScheduler),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue