From ec10a0bf87b07b2a7fdee5070e3276030c28d94f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Dec 2025 18:04:26 +0100 Subject: [PATCH] Add unit test on RustLinkDesktopHandler Add unit test on RustLinkMobileHandler Add unit test on DefaultLinkNewDeviceEntryPoint --- .../DefaultLinkNewDeviceEntryPointTest.kt | 48 ++++++++ .../libraries/matrix/impl/RustMatrixClient.kt | 2 + .../impl/linknewdevice/QrCodeDataParser.kt | 20 ++++ .../linknewdevice/RustLinkDesktopHandler.kt | 4 +- .../fixtures/fakes/FakeFfiCheckCodeSender.kt | 13 +++ .../FakeFfiGrantLoginWithQrCodeHandler.kt | 41 +++++++ .../impl/fixtures/fakes/FakeFfiQrCodeData.kt | 5 + .../linknewdevice/FakeQrCodeDataParser.kt | 17 +++ .../RustLinkDesktopHandlerTest.kt | 109 ++++++++++++++++++ .../RustLinkMobileHandlerTest.kt | 91 +++++++++++++++ 10 files changed, 348 insertions(+), 2 deletions(-) create mode 100644 features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/DefaultLinkNewDeviceEntryPointTest.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/QrCodeDataParser.kt create mode 100644 libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiCheckCodeSender.kt create mode 100644 libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiGrantLoginWithQrCodeHandler.kt create mode 100644 libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/FakeQrCodeDataParser.kt create mode 100644 libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkDesktopHandlerTest.kt create mode 100644 libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkMobileHandlerTest.kt diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/DefaultLinkNewDeviceEntryPointTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/DefaultLinkNewDeviceEntryPointTest.kt new file mode 100644 index 0000000000..2957a89495 --- /dev/null +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/DefaultLinkNewDeviceEntryPointTest.kt @@ -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) + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 1de606637a..6ff66c0f71 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -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(), ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/QrCodeDataParser.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/QrCodeDataParser.kt new file mode 100644 index 0000000000..6472850a8c --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/QrCodeDataParser.kt @@ -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) + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkDesktopHandler.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkDesktopHandler.kt index fd37b73ad8..211bdc3d4e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkDesktopHandler.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkDesktopHandler.kt @@ -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.Uninitialized) override val linkDesktopStep: StateFlow = _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 { diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiCheckCodeSender.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiCheckCodeSender.kt new file mode 100644 index 0000000000..4bc6c212d2 --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiCheckCodeSender.kt @@ -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) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiGrantLoginWithQrCodeHandler.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiGrantLoginWithQrCodeHandler.kt new file mode 100644 index 0000000000..cd0733695b --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiGrantLoginWithQrCodeHandler.kt @@ -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) + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiQrCodeData.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiQrCodeData.kt index d377643fa7..4070d1b2cd 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiQrCodeData.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiQrCodeData.kt @@ -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() + } } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/FakeQrCodeDataParser.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/FakeQrCodeDataParser.kt new file mode 100644 index 0000000000..254258fcf7 --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/FakeQrCodeDataParser.kt @@ -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() + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkDesktopHandlerTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkDesktopHandlerTest.kt new file mode 100644 index 0000000000..a180e4d515 --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkDesktopHandlerTest.kt @@ -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(), + ) +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkMobileHandlerTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkMobileHandlerTest.kt new file mode 100644 index 0000000000..aa13996e8a --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkMobileHandlerTest.kt @@ -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), + ) +}