From e6eb86538fe0cace77092ca4dd508ecb8242479f Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 27 Sep 2021 16:57:05 +0100 Subject: [PATCH 1/7] wrapping the Dispatcher.IO and making it injectable for testing --- .../im/vector/app/core/di/VectorComponent.kt | 3 +++ .../im/vector/app/core/di/VectorModule.kt | 8 +++++++ .../core/dispatchers/CoroutineDispatchers.kt | 22 +++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/core/dispatchers/CoroutineDispatchers.kt diff --git a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt index ca26c99a15..a8bf128367 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt @@ -26,6 +26,7 @@ import im.vector.app.EmojiCompatFontProvider import im.vector.app.EmojiCompatWrapper import im.vector.app.VectorApplication import im.vector.app.core.dialogs.UnrecognizedCertificateDialog +import im.vector.app.core.dispatchers.CoroutineDispatchers import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.network.WifiDetector import im.vector.app.core.pushers.PushersManager @@ -171,6 +172,8 @@ interface VectorComponent { fun appCoroutineScope(): CoroutineScope + fun coroutineDispatchers(): CoroutineDispatchers + fun jitsiActiveConferenceHolder(): JitsiActiveConferenceHolder @Component.Factory diff --git a/vector/src/main/java/im/vector/app/core/di/VectorModule.kt b/vector/src/main/java/im/vector/app/core/di/VectorModule.kt index dd1ffee8ec..c552d8a5db 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorModule.kt @@ -23,6 +23,7 @@ import android.content.res.Resources import dagger.Binds import dagger.Module import dagger.Provides +import im.vector.app.core.dispatchers.CoroutineDispatchers import im.vector.app.core.error.DefaultErrorFormatter import im.vector.app.core.error.ErrorFormatter import im.vector.app.features.invite.AutoAcceptInvites @@ -33,6 +34,7 @@ import im.vector.app.features.pin.PinCodeStore import im.vector.app.features.pin.SharedPrefPinCodeStore import im.vector.app.features.ui.SharedPreferencesUiStateRepository import im.vector.app.features.ui.UiStateRepository +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -105,6 +107,12 @@ abstract class VectorModule { fun providesApplicationCoroutineScope(): CoroutineScope { return CoroutineScope(SupervisorJob() + Dispatchers.Main) } + + @Provides + @JvmStatic + fun providesCoroutineDispatchers(): CoroutineDispatchers { + return CoroutineDispatchers(io = Dispatchers.IO) + } } @Binds diff --git a/vector/src/main/java/im/vector/app/core/dispatchers/CoroutineDispatchers.kt b/vector/src/main/java/im/vector/app/core/dispatchers/CoroutineDispatchers.kt new file mode 100644 index 0000000000..c489290a55 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/dispatchers/CoroutineDispatchers.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.dispatchers + +import kotlinx.coroutines.CoroutineDispatcher +import javax.inject.Inject + +data class CoroutineDispatchers @Inject constructor(val io: CoroutineDispatcher) From 789cc6b597b61acf420f8943d84b318cef27c941 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 27 Sep 2021 17:16:36 +0100 Subject: [PATCH 2/7] exlcuding the slf4j logger from the test dependencies the videocache dependency includes slf4j which in turn causes mockk to reflectively attempt to call real Log functions, which crashes the units tests due to the stub android.jar --- vector/build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vector/build.gradle b/vector/build.gradle index d60f928f2c..1b28422eca 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -314,6 +314,11 @@ android { } } +configurations { + // videocache includes a sl4j logger which causes mockk to attempt to call the static android Log + testImplementation.exclude group: 'org.slf4j', module: 'slf4j-android' +} + dependencies { implementation project(":matrix-sdk-android") @@ -490,6 +495,7 @@ dependencies { // TESTS testImplementation libs.tests.junit testImplementation libs.tests.kluent + testImplementation libs.mockk.mockk // Plant Timber tree for test testImplementation libs.tests.timberJunitRule From ac0c7067e0e3af3970ae1afbf68a23170f5f56a8 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 27 Sep 2021 17:18:13 +0100 Subject: [PATCH 3/7] updating the keys exporter to validate the generated file size in an attempt to warn the user of malformed outputs - injects the io dispatcher to allow the testing - adds unit tests around the different error flows --- .../app/features/crypto/keys/KeysExporter.kt | 26 +++- .../features/crypto/keys/KeysExporterTest.kt | 137 ++++++++++++++++++ 2 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt index 2c66a14cb4..53ec517d47 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt @@ -18,6 +18,7 @@ package im.vector.app.features.crypto.keys import android.content.Context import android.net.Uri +import im.vector.app.core.dispatchers.CoroutineDispatchers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.session.Session @@ -25,17 +26,36 @@ import javax.inject.Inject class KeysExporter @Inject constructor( private val session: Session, - private val context: Context + private val context: Context, + private val dispatchers: CoroutineDispatchers ) { /** * Export keys and write them to the provided uri */ suspend fun export(password: String, uri: Uri) { - return withContext(Dispatchers.IO) { + withContext(dispatchers.io) { val data = session.cryptoService().exportRoomKeys(password) context.contentResolver.openOutputStream(uri) ?.use { it.write(data) } - ?: throw IllegalStateException("Unable to open file for writting") + ?: throw IllegalStateException("Unable to open file for writing") + verifyExportedKeysOutputFileSize(uri, expectedSize = data.size.toLong()) + } + } + + private fun verifyExportedKeysOutputFileSize(uri: Uri, expectedSize: Long) { + val output = context.contentResolver.openFileDescriptor(uri, "r", null) + when { + output == null -> throw IllegalStateException("Exported file not found") + output.statSize != expectedSize -> { + throw UnexpectedExportKeysFileSizeException( + expectedFileSize = output.statSize, + actualFileSize = expectedSize, + ) + } } } } + +class UnexpectedExportKeysFileSizeException(expectedFileSize: Long, actualFileSize: Long) : IllegalStateException( + "Exported Keys file has unexpected file size, got: $actualFileSize but expected: $expectedFileSize" +) diff --git a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt new file mode 100644 index 0000000000..630a96eb36 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.crypto.keys + +import android.content.ContentResolver +import android.content.Context +import android.net.Uri +import android.os.ParcelFileDescriptor +import im.vector.app.core.dispatchers.CoroutineDispatchers +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import org.amshove.kluent.internal.assertFailsWith +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.CryptoService +import java.io.OutputStream + +private val A_URI = mockk() +private val A_ROOM_KEYS_EXPORT = ByteArray(size = 111) +private const val A_PASSWORD = "a password" + +class KeysExporterTest { + + private val cryptoService = FakeCryptoService() + private val context = FakeContext() + private val keysExporter = KeysExporter( + session = FakeSession(cryptoService = cryptoService), + context = context.instance, + dispatchers = CoroutineDispatchers(Dispatchers.Unconfined), + ) + + @Before + fun setUp() { + cryptoService.roomKeysExport = A_ROOM_KEYS_EXPORT + } + + @Test + fun `when exporting then writes exported keys to context output stream`() { + givenFileDescriptorWithSize(size = A_ROOM_KEYS_EXPORT.size.toLong()) + val outputStream = context.givenOutputStreamFor(A_URI) + + runBlocking { keysExporter.export(A_PASSWORD, A_URI) } + + verify { outputStream.write(A_ROOM_KEYS_EXPORT) } + } + + @Test + fun `given different file size returned for export when exporting then throws UnexpectedExportKeysFileSizeException`() { + givenFileDescriptorWithSize(size = 110) + context.givenOutputStreamFor(A_URI) + + assertFailsWith { + runBlocking { keysExporter.export(A_PASSWORD, A_URI) } + } + } + + @Test + fun `given output stream is unavailable for exporting to when exporting then throws IllegalStateException`() { + context.givenMissingOutputStreamFor(A_URI) + + assertFailsWith(message = "Unable to open file for writing") { + runBlocking { keysExporter.export(A_PASSWORD, A_URI) } + } + } + + @Test + fun `given exported file is missing after export when exporting then throws IllegalStateException`() { + context.givenFileDescriptor(A_URI, mode = "r") { null } + context.givenOutputStreamFor(A_URI) + + assertFailsWith(message = "Exported file not found") { + runBlocking { keysExporter.export(A_PASSWORD, A_URI) } + } + } + + private fun givenFileDescriptorWithSize(size: Long) { + context.givenFileDescriptor(A_URI, mode = "r") { + mockk().also { every { it.statSize } returns size } + } + } +} + +class FakeContext { + + private val contentResolver = mockk() + val instance = mockk() + + init { + every { instance.contentResolver } returns contentResolver + } + + fun givenFileDescriptor(uri: Uri, mode: String, factory: () -> ParcelFileDescriptor?) { + val fileDescriptor = factory() + every { contentResolver.openFileDescriptor(uri, mode, null) } returns fileDescriptor + } + + fun givenOutputStreamFor(uri: Uri): OutputStream { + val outputStream = mockk(relaxed = true) + every { contentResolver.openOutputStream(uri) } returns outputStream + return outputStream + } + + fun givenMissingOutputStreamFor(uri: Uri) { + every { contentResolver.openOutputStream(uri) } returns null + } +} + +class FakeSession( + private val cryptoService: CryptoService = FakeCryptoService() +) : Session by mockk(relaxed = true) { + override fun cryptoService() = cryptoService +} + +class FakeCryptoService : CryptoService by mockk() { + + var roomKeysExport = ByteArray(size = 1) + + override suspend fun exportRoomKeys(password: String) = roomKeysExport +} From 509c61c1a85df0a2623a6b631d8b9c7e2f483aed Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 27 Sep 2021 17:19:39 +0100 Subject: [PATCH 4/7] extracting the test fakes to their own package --- .../features/crypto/keys/KeysExporterTest.kt | 46 ++--------------- .../im/vector/app/test/fakes/FakeContext.kt | 50 +++++++++++++++++++ .../app/test/fakes/FakeCryptoService.kt | 27 ++++++++++ .../im/vector/app/test/fakes/FakeSession.kt | 27 ++++++++++ 4 files changed, 107 insertions(+), 43 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt diff --git a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt index 630a96eb36..130dbde73e 100644 --- a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt +++ b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt @@ -16,11 +16,12 @@ package im.vector.app.features.crypto.keys -import android.content.ContentResolver -import android.content.Context import android.net.Uri import android.os.ParcelFileDescriptor import im.vector.app.core.dispatchers.CoroutineDispatchers +import im.vector.app.test.fakes.FakeContext +import im.vector.app.test.fakes.FakeCryptoService +import im.vector.app.test.fakes.FakeSession import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -29,9 +30,6 @@ import kotlinx.coroutines.runBlocking import org.amshove.kluent.internal.assertFailsWith import org.junit.Before import org.junit.Test -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.crypto.CryptoService -import java.io.OutputStream private val A_URI = mockk() private val A_ROOM_KEYS_EXPORT = ByteArray(size = 111) @@ -97,41 +95,3 @@ class KeysExporterTest { } } } - -class FakeContext { - - private val contentResolver = mockk() - val instance = mockk() - - init { - every { instance.contentResolver } returns contentResolver - } - - fun givenFileDescriptor(uri: Uri, mode: String, factory: () -> ParcelFileDescriptor?) { - val fileDescriptor = factory() - every { contentResolver.openFileDescriptor(uri, mode, null) } returns fileDescriptor - } - - fun givenOutputStreamFor(uri: Uri): OutputStream { - val outputStream = mockk(relaxed = true) - every { contentResolver.openOutputStream(uri) } returns outputStream - return outputStream - } - - fun givenMissingOutputStreamFor(uri: Uri) { - every { contentResolver.openOutputStream(uri) } returns null - } -} - -class FakeSession( - private val cryptoService: CryptoService = FakeCryptoService() -) : Session by mockk(relaxed = true) { - override fun cryptoService() = cryptoService -} - -class FakeCryptoService : CryptoService by mockk() { - - var roomKeysExport = ByteArray(size = 1) - - override suspend fun exportRoomKeys(password: String) = roomKeysExport -} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt new file mode 100644 index 0000000000..8dec510f5a --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import android.content.ContentResolver +import android.content.Context +import android.net.Uri +import android.os.ParcelFileDescriptor +import io.mockk.every +import io.mockk.mockk +import java.io.OutputStream + +class FakeContext { + + private val contentResolver = mockk() + val instance = mockk() + + init { + every { instance.contentResolver } returns contentResolver + } + + fun givenFileDescriptor(uri: Uri, mode: String, factory: () -> ParcelFileDescriptor?) { + val fileDescriptor = factory() + every { contentResolver.openFileDescriptor(uri, mode, null) } returns fileDescriptor + } + + fun givenOutputStreamFor(uri: Uri): OutputStream { + val outputStream = mockk(relaxed = true) + every { contentResolver.openOutputStream(uri) } returns outputStream + return outputStream + } + + fun givenMissingOutputStreamFor(uri: Uri) { + every { contentResolver.openOutputStream(uri) } returns null + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt new file mode 100644 index 0000000000..735af4ea11 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import io.mockk.mockk +import org.matrix.android.sdk.api.session.crypto.CryptoService + +class FakeCryptoService : CryptoService by mockk() { + + var roomKeysExport = ByteArray(size = 1) + + override suspend fun exportRoomKeys(password: String) = roomKeysExport +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt new file mode 100644 index 0000000000..3400436705 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import io.mockk.mockk +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.CryptoService + +class FakeSession( + private val cryptoService: CryptoService = FakeCryptoService() +) : Session by mockk(relaxed = true) { + override fun cryptoService() = cryptoService +} From 6df03fc13b2299485884db7a29240db13f65ae44 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 27 Sep 2021 17:28:06 +0100 Subject: [PATCH 5/7] adding changelog entry --- changelog.d/4082.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4082.bugfix diff --git a/changelog.d/4082.bugfix b/changelog.d/4082.bugfix new file mode 100644 index 0000000000..9ec8d4db97 --- /dev/null +++ b/changelog.d/4082.bugfix @@ -0,0 +1 @@ +Verifying exported E2E keys to provide user feedback when the output is malformed \ No newline at end of file From 19d1d981c32e899a5bfe8995e102cbf0255c6c17 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 27 Sep 2021 17:39:21 +0100 Subject: [PATCH 6/7] linting --- vector/src/main/java/im/vector/app/core/di/VectorModule.kt | 1 - .../java/im/vector/app/features/crypto/keys/KeysExporter.kt | 3 +-- .../im/vector/app/features/crypto/keys/KeysExporterTest.kt | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/di/VectorModule.kt b/vector/src/main/java/im/vector/app/core/di/VectorModule.kt index c552d8a5db..ddb765cef8 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorModule.kt @@ -34,7 +34,6 @@ import im.vector.app.features.pin.PinCodeStore import im.vector.app.features.pin.SharedPrefPinCodeStore import im.vector.app.features.ui.SharedPreferencesUiStateRepository import im.vector.app.features.ui.UiStateRepository -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt index 53ec517d47..8eb4fafc68 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt @@ -19,7 +19,6 @@ package im.vector.app.features.crypto.keys import android.content.Context import android.net.Uri import im.vector.app.core.dispatchers.CoroutineDispatchers -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.session.Session import javax.inject.Inject @@ -49,7 +48,7 @@ class KeysExporter @Inject constructor( output.statSize != expectedSize -> { throw UnexpectedExportKeysFileSizeException( expectedFileSize = output.statSize, - actualFileSize = expectedSize, + actualFileSize = expectedSize ) } } diff --git a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt index 130dbde73e..a8997db855 100644 --- a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt +++ b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt @@ -42,7 +42,7 @@ class KeysExporterTest { private val keysExporter = KeysExporter( session = FakeSession(cryptoService = cryptoService), context = context.instance, - dispatchers = CoroutineDispatchers(Dispatchers.Unconfined), + dispatchers = CoroutineDispatchers(Dispatchers.Unconfined) ) @Before From 399b2a13ee1803d4c8247181cae1fd5d5ba43e64 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 27 Sep 2021 18:08:32 +0100 Subject: [PATCH 7/7] fixing exception message parameter ordering --- .../java/im/vector/app/features/crypto/keys/KeysExporter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt index 8eb4fafc68..3db67df8e1 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt @@ -47,8 +47,8 @@ class KeysExporter @Inject constructor( output == null -> throw IllegalStateException("Exported file not found") output.statSize != expectedSize -> { throw UnexpectedExportKeysFileSizeException( - expectedFileSize = output.statSize, - actualFileSize = expectedSize + expectedFileSize = expectedSize, + actualFileSize = output.statSize ) } }