From 9db15f2ff51167af25b63b7a9a69bc7d4eae6840 Mon Sep 17 00:00:00 2001 From: boris Date: Fri, 16 Aug 2024 12:00:24 +0300 Subject: [PATCH] fix: Check file extension instead of mimeType [WPB-10605] (#2950) * fix: Check file extension instead of mimeType * Review updates --- .../kalium/logic/feature/UserSessionScope.kt | 6 +- .../asset/ScheduleNewAssetMessageUseCase.kt | 4 +- .../asset/ValidateAssetFileTypeUseCase.kt | 42 ++++++ .../asset/ValidateAssetMimeTypeUseCase.kt | 36 ------ .../logic/feature/message/MessageScope.kt | 8 +- .../receiver/asset/AssetMessageHandler.kt | 6 +- .../ScheduleNewAssetMessageUseCaseTest.kt | 8 +- .../asset/ValidateAssetFileTypeUseCaseTest.kt | 87 +++++++++++++ .../receiver/asset/AssetMessageHandlerTest.kt | 122 +++++++++++++++++- 9 files changed, 264 insertions(+), 55 deletions(-) create mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/asset/ValidateAssetFileTypeUseCase.kt delete mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/asset/ValidateAssetMimeTypeUseCase.kt create mode 100644 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/asset/ValidateAssetFileTypeUseCaseTest.kt diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt index b3ecde01d32..7527415262b 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt @@ -153,8 +153,8 @@ import com.wire.kalium.logic.feature.applock.AppLockTeamFeatureConfigObserver import com.wire.kalium.logic.feature.applock.AppLockTeamFeatureConfigObserverImpl import com.wire.kalium.logic.feature.applock.MarkTeamAppLockStatusAsNotifiedUseCase import com.wire.kalium.logic.feature.applock.MarkTeamAppLockStatusAsNotifiedUseCaseImpl -import com.wire.kalium.logic.feature.asset.ValidateAssetMimeTypeUseCase -import com.wire.kalium.logic.feature.asset.ValidateAssetMimeTypeUseCaseImpl +import com.wire.kalium.logic.feature.asset.ValidateAssetFileTypeUseCase +import com.wire.kalium.logic.feature.asset.ValidateAssetFileTypeUseCaseImpl import com.wire.kalium.logic.feature.auth.AuthenticationScope import com.wire.kalium.logic.feature.auth.AuthenticationScopeProvider import com.wire.kalium.logic.feature.auth.ClearUserDataUseCase @@ -1796,7 +1796,7 @@ class UserSessionScope internal constructor( private val clearUserData: ClearUserDataUseCase get() = ClearUserDataUseCaseImpl(userStorage) - private val validateAssetMimeType: ValidateAssetMimeTypeUseCase get() = ValidateAssetMimeTypeUseCaseImpl() + private val validateAssetMimeType: ValidateAssetFileTypeUseCase get() = ValidateAssetFileTypeUseCaseImpl() val logout: LogoutUseCase get() = LogoutUseCaseImpl( diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/asset/ScheduleNewAssetMessageUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/asset/ScheduleNewAssetMessageUseCase.kt index 06532a015c5..a81023e202d 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/asset/ScheduleNewAssetMessageUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/asset/ScheduleNewAssetMessageUseCase.kt @@ -110,7 +110,7 @@ internal class ScheduleNewAssetMessageUseCaseImpl( private val selfDeleteTimer: ObserveSelfDeletionTimerSettingsForConversationUseCase, private val scope: CoroutineScope, private val observeFileSharingStatus: ObserveFileSharingStatusUseCase, - private val validateAssetMimeTypeUseCase: ValidateAssetMimeTypeUseCase, + private val validateAssetFileUseCase: ValidateAssetFileTypeUseCase, private val dispatcher: KaliumDispatcher, ) : ScheduleNewAssetMessageUseCase { @@ -133,7 +133,7 @@ internal class ScheduleNewAssetMessageUseCaseImpl( FileSharingStatus.Value.EnabledAll -> { /* no-op*/ } - is FileSharingStatus.Value.EnabledSome -> if (!validateAssetMimeTypeUseCase(assetMimeType, it.state.allowedType)) { + is FileSharingStatus.Value.EnabledSome -> if (!validateAssetFileUseCase(assetName, it.state.allowedType)) { kaliumLogger.e("The asset message trying to be processed has invalid content data") return ScheduleNewAssetMessageResult.Failure.RestrictedFileType } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/asset/ValidateAssetFileTypeUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/asset/ValidateAssetFileTypeUseCase.kt new file mode 100644 index 00000000000..6a15510cdc5 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/asset/ValidateAssetFileTypeUseCase.kt @@ -0,0 +1,42 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 com.wire.kalium.logic.feature.asset + +/** + * Returns true if the file extension is present in file name and is allowed and false otherwise. + * @param fileName the file name (with extension) to validate. + * @param allowedExtension the list of allowed extension. + */ +interface ValidateAssetFileTypeUseCase { + operator fun invoke(fileName: String?, allowedExtension: List): Boolean +} + +internal class ValidateAssetFileTypeUseCaseImpl : ValidateAssetFileTypeUseCase { + override operator fun invoke(fileName: String?, allowedExtension: List): Boolean { + if (fileName == null) return false + + val split = fileName.split(".") + return if (split.size < 2) { + false + } else { + val allowedExtensionLowerCase = allowedExtension.map { it.lowercase() } + val extensions = split.subList(1, split.size).map { it.lowercase() } + extensions.all { it.isNotEmpty() && allowedExtensionLowerCase.contains(it) } + } + } +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/asset/ValidateAssetMimeTypeUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/asset/ValidateAssetMimeTypeUseCase.kt deleted file mode 100644 index c8a4ab19c4b..00000000000 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/asset/ValidateAssetMimeTypeUseCase.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Wire - * Copyright (C) 2024 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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 com.wire.kalium.logic.feature.asset - -/** - * Returns true if the mime type is allowed and false otherwise. - * @param mimeType the mime type to validate. - * @param allowedExtension the list of allowed extension. - */ -interface ValidateAssetMimeTypeUseCase { - operator fun invoke(mimeType: String, allowedExtension: List): Boolean -} - -internal class ValidateAssetMimeTypeUseCaseImpl : ValidateAssetMimeTypeUseCase { - override operator fun invoke(mimeType: String, allowedExtension: List): Boolean { - val extension = mimeType.split("/").last().lowercase() - return allowedExtension.any { - it.lowercase() == extension - } - } -} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageScope.kt index 8f36dfbe885..0895c992136 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageScope.kt @@ -56,8 +56,8 @@ import com.wire.kalium.logic.feature.asset.UpdateAssetMessageDownloadStatusUseCa import com.wire.kalium.logic.feature.asset.UpdateAssetMessageDownloadStatusUseCaseImpl import com.wire.kalium.logic.feature.asset.UpdateAssetMessageUploadStatusUseCase import com.wire.kalium.logic.feature.asset.UpdateAssetMessageUploadStatusUseCaseImpl -import com.wire.kalium.logic.feature.asset.ValidateAssetMimeTypeUseCase -import com.wire.kalium.logic.feature.asset.ValidateAssetMimeTypeUseCaseImpl +import com.wire.kalium.logic.feature.asset.ValidateAssetFileTypeUseCase +import com.wire.kalium.logic.feature.asset.ValidateAssetFileTypeUseCaseImpl import com.wire.kalium.logic.feature.message.composite.SendButtonActionMessageUseCase import com.wire.kalium.logic.feature.message.ephemeral.DeleteEphemeralMessageForSelfUserAsReceiverUseCaseImpl import com.wire.kalium.logic.feature.message.ephemeral.DeleteEphemeralMessageForSelfUserAsSenderUseCaseImpl @@ -144,8 +144,8 @@ class MessageScope internal constructor( protoContentMapper = protoContentMapper ) - private val validateAssetMimeTypeUseCase: ValidateAssetMimeTypeUseCase - get() = ValidateAssetMimeTypeUseCaseImpl() + private val validateAssetMimeTypeUseCase: ValidateAssetFileTypeUseCase + get() = ValidateAssetFileTypeUseCaseImpl() private val messageContentEncoder = MessageContentEncoder() private val messageSendingInterceptor: MessageSendingInterceptor diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/asset/AssetMessageHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/asset/AssetMessageHandler.kt index a5fd1737a2f..f76d5004c07 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/asset/AssetMessageHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/asset/AssetMessageHandler.kt @@ -24,7 +24,7 @@ import com.wire.kalium.logic.data.message.Message import com.wire.kalium.logic.data.message.MessageContent import com.wire.kalium.logic.data.message.MessageRepository import com.wire.kalium.logic.data.message.PersistMessageUseCase -import com.wire.kalium.logic.feature.asset.ValidateAssetMimeTypeUseCase +import com.wire.kalium.logic.feature.asset.ValidateAssetFileTypeUseCase import com.wire.kalium.logic.functional.onFailure import com.wire.kalium.logic.functional.onSuccess import com.wire.kalium.logic.kaliumLogger @@ -38,7 +38,7 @@ internal class AssetMessageHandlerImpl( private val messageRepository: MessageRepository, private val persistMessage: PersistMessageUseCase, private val userConfigRepository: UserConfigRepository, - private val validateAssetMimeTypeUseCase: ValidateAssetMimeTypeUseCase + private val validateAssetMimeTypeUseCase: ValidateAssetFileTypeUseCase ) : AssetMessageHandler { override suspend fun handle(message: Message.Regular) { @@ -53,7 +53,7 @@ internal class AssetMessageHandlerImpl( FileSharingStatus.Value.EnabledAll -> true is FileSharingStatus.Value.EnabledSome -> validateAssetMimeTypeUseCase( - messageContent.value.mimeType, + messageContent.value.name, it.state.allowedType ) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/asset/ScheduleNewAssetMessageUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/asset/ScheduleNewAssetMessageUseCaseTest.kt index fec3e956ca7..9c98a282d70 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/asset/ScheduleNewAssetMessageUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/asset/ScheduleNewAssetMessageUseCaseTest.kt @@ -628,7 +628,7 @@ class ScheduleNewAssetMessageUseCaseTest { verify(arrangement.validateAssetMimeTypeUseCase) .function(arrangement.validateAssetMimeTypeUseCase::invoke) - .with(eq("text/plain"), eq(listOf("png"))) + .with(eq("some-asset.txt"), eq(listOf("png"))) .wasInvoked(exactly = once) } @@ -636,7 +636,7 @@ class ScheduleNewAssetMessageUseCaseTest { fun givenAssetMimeTypeRestrictedAndFileAllowed_whenSending_thenReturnSendTheFile() = runTest(testDispatcher.default) { // Given val assetToSend = mockedLongAssetData() - val assetName = "some-asset.txt" + val assetName = "some-asset.png" val inputDataPath = fakeKaliumFileSystem.providePersistentAssetPath(assetName) val expectedAssetId = dummyUploadedAssetId val expectedAssetSha256 = SHA256Key("some-asset-sha-256".toByteArray()) @@ -669,7 +669,7 @@ class ScheduleNewAssetMessageUseCaseTest { verify(arrangement.validateAssetMimeTypeUseCase) .function(arrangement.validateAssetMimeTypeUseCase::invoke) - .with(eq("image/png"), eq(listOf("png"))) + .with(eq("some-asset.png"), eq(listOf("png"))) .wasInvoked(exactly = once) } @@ -706,7 +706,7 @@ class ScheduleNewAssetMessageUseCaseTest { private val messageRepository: MessageRepository = mock(MessageRepository::class) @Mock - val validateAssetMimeTypeUseCase: ValidateAssetMimeTypeUseCase = mock(ValidateAssetMimeTypeUseCase::class) + val validateAssetMimeTypeUseCase: ValidateAssetFileTypeUseCase = mock(ValidateAssetFileTypeUseCase::class) @Mock val observerFileSharingStatusUseCase: ObserveFileSharingStatusUseCase = mock(ObserveFileSharingStatusUseCase::class) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/asset/ValidateAssetFileTypeUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/asset/ValidateAssetFileTypeUseCaseTest.kt new file mode 100644 index 00000000000..613f57bafe0 --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/asset/ValidateAssetFileTypeUseCaseTest.kt @@ -0,0 +1,87 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 com.wire.kalium.logic.feature.asset + +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ValidateAssetFileTypeUseCaseTest { + + @Test + fun givenRegularFileNameWithAllowedExtension_whenInvoke_thenBeApproved() = runTest { + val (_, validate) = arrange {} + + val result = validate("name.txt", listOf("txt", "jpg")) + + assertTrue(result) + } + + @Test + fun givenRegularFileNameWithNOTAllowedExtension_whenInvoke_thenBeRestricted() = runTest { + val (_, validate) = arrange {} + + val result = validate("name.php", listOf("txt", "jpg")) + + assertFalse(result) + } + + @Test + fun givenRegularFileNameWithoutExtension_whenInvoke_thenBeRestricted() = runTest { + val (_, validate) = arrange {} + + val result = validate("name", listOf("txt", "jpg")) + + assertFalse(result) + } + + @Test + fun givenNullFileName_whenInvoke_thenBeRestricted() = runTest { + val (_, validate) = arrange {} + + val result = validate(null, listOf("txt", "jpg")) + + assertFalse(result) + } + + @Test + fun givenRegularFileNameWithFewExtensions_whenInvoke_thenEachExtensionIsChecked() = runTest { + val (_, validate) = arrange {} + + val result1 = validate("name.php.txt", listOf("txt", "jpg")) + val result2 = validate("name.txt.php", listOf("txt", "jpg")) + val result3 = validate("name..txt.jpg", listOf("txt", "jpg")) + val result4 = validate("name.txt.php.txt.jpg", listOf("txt", "jpg")) + + assertFalse(result1) + assertFalse(result2) + assertFalse(result3) + assertFalse(result4) + } + + private fun arrange(block: Arrangement.() -> Unit) = Arrangement(block).arrange() + + private class Arrangement( + private val block: Arrangement.() -> Unit + ) { + fun arrange() = block().run { + this@Arrangement to ValidateAssetFileTypeUseCaseImpl() + } + } +} diff --git a/logic/src/jvmTest/kotlin/com/wire/kalium/logic/sync/receiver/asset/AssetMessageHandlerTest.kt b/logic/src/jvmTest/kotlin/com/wire/kalium/logic/sync/receiver/asset/AssetMessageHandlerTest.kt index 52eb32f8075..ee0d2d9c1f4 100644 --- a/logic/src/jvmTest/kotlin/com/wire/kalium/logic/sync/receiver/asset/AssetMessageHandlerTest.kt +++ b/logic/src/jvmTest/kotlin/com/wire/kalium/logic/sync/receiver/asset/AssetMessageHandlerTest.kt @@ -29,7 +29,7 @@ import com.wire.kalium.logic.data.message.MessageEncryptionAlgorithm import com.wire.kalium.logic.data.message.MessageRepository import com.wire.kalium.logic.data.message.PersistMessageUseCase import com.wire.kalium.logic.data.user.UserId -import com.wire.kalium.logic.feature.asset.ValidateAssetMimeTypeUseCase +import com.wire.kalium.logic.feature.asset.ValidateAssetFileTypeUseCase import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.sync.receiver.conversation.message.hasValidData import com.wire.kalium.logic.sync.receiver.conversation.message.hasValidRemoteData @@ -44,7 +44,6 @@ import io.mockative.once import io.mockative.verify import junit.framework.TestCase.assertFalse import junit.framework.TestCase.assertTrue -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test @@ -254,6 +253,123 @@ class AssetMessageHandlerTest { .wasInvoked(exactly = once) } + @Test + fun givenValidPreviewAssetMessageStoredAndExtensionIsAllowed_whenHandlingTheUpdate_itIsCorrectlyProcessedAndVisible() = runTest { + // Given + val previewAssetMessage = PREVIEW_ASSET_MESSAGE.copy(visibility = Message.Visibility.HIDDEN) + val updateAssetMessage = COMPLETE_ASSET_MESSAGE + val isFileSharingEnabled = FileSharingStatus.Value.EnabledSome(listOf("txt", "png", "zip")) + val (arrangement, assetMessageHandler) = Arrangement() + .withSuccessfulFileSharingFlag(isFileSharingEnabled) + .withValidateAssetMime(true) + .withSuccessfulStoredMessage(previewAssetMessage) + .withSuccessfulPersistMessageUseCase(updateAssetMessage) + .arrange() + + // When + assetMessageHandler.handle(updateAssetMessage) + + // Then + assertFalse((previewAssetMessage.content as MessageContent.Asset).value.hasValidRemoteData()) + assertTrue((updateAssetMessage.content as MessageContent.Asset).value.remoteData.hasValidData()) + verify(arrangement.persistMessage) + .suspendFunction(arrangement.persistMessage::invoke) + .with(matching { + it.id == updateAssetMessage.id + && it.conversationId.toString() == updateAssetMessage.conversationId.toString() + && it.visibility == Message.Visibility.VISIBLE + }) + .wasInvoked(exactly = once) + + verify(arrangement.messageRepository) + .suspendFunction(arrangement.messageRepository::getMessageById) + .with(eq(previewAssetMessage.conversationId), eq(previewAssetMessage.id)) + .wasInvoked(exactly = once) + + verify(arrangement.validateAssetMimeType) + .suspendFunction(arrangement.validateAssetMimeType::invoke) + .with(eq(COMPLETE_ASSET_CONTENT.value.name), eq(isFileSharingEnabled.allowedType)) + .wasInvoked(exactly = once) + } + + @Test + fun givenValidPreviewAssetMessageStoredAndExtensionIsNotAllowed_whenHandlingTheUpdate_itIsProcessedButNoVisible() = runTest { + // Given + val previewAssetMessage = PREVIEW_ASSET_MESSAGE.copy(visibility = Message.Visibility.HIDDEN) + val updateAssetMessage = COMPLETE_ASSET_MESSAGE + val isFileSharingEnabled = FileSharingStatus.Value.EnabledSome(listOf("txt", "png")) + val (arrangement, assetMessageHandler) = Arrangement() + .withSuccessfulFileSharingFlag(isFileSharingEnabled) + .withValidateAssetMime(true) + .withSuccessfulStoredMessage(previewAssetMessage) + .withSuccessfulPersistMessageUseCase(updateAssetMessage) + .arrange() + + // When + assetMessageHandler.handle(updateAssetMessage) + + // Then + assertFalse((previewAssetMessage.content as MessageContent.Asset).value.hasValidRemoteData()) + assertTrue((updateAssetMessage.content as MessageContent.Asset).value.remoteData.hasValidData()) + verify(arrangement.persistMessage) + .suspendFunction(arrangement.persistMessage::invoke) + .with(matching { + it.id == updateAssetMessage.id + && it.conversationId.toString() == updateAssetMessage.conversationId.toString() + && it.visibility == updateAssetMessage.visibility + }) + .wasInvoked(exactly = once) + + verify(arrangement.messageRepository) + .suspendFunction(arrangement.messageRepository::getMessageById) + .with(eq(previewAssetMessage.conversationId), eq(previewAssetMessage.id)) + .wasInvoked(exactly = once) + + verify(arrangement.validateAssetMimeType) + .suspendFunction(arrangement.validateAssetMimeType::invoke) + .with(eq(COMPLETE_ASSET_CONTENT.value.name), eq(isFileSharingEnabled.allowedType)) + .wasInvoked(exactly = once) + } + + @Test + fun givenValidPreviewAssetMessageStoredButFileSharingRestricted_whenHandlingTheUpdate_itIsProcessedButNoVisible() = runTest { + // Given + val previewAssetMessage = PREVIEW_ASSET_MESSAGE.copy(visibility = Message.Visibility.HIDDEN) + val updateAssetMessage = COMPLETE_ASSET_MESSAGE + val isFileSharingEnabled = FileSharingStatus.Value.Disabled + val (arrangement, assetMessageHandler) = Arrangement() + .withSuccessfulFileSharingFlag(isFileSharingEnabled) + .withValidateAssetMime(true) + .withSuccessfulStoredMessage(previewAssetMessage) + .withSuccessfulPersistMessageUseCase(updateAssetMessage) + .arrange() + + // When + assetMessageHandler.handle(updateAssetMessage) + + // Then + assertFalse((previewAssetMessage.content as MessageContent.Asset).value.hasValidRemoteData()) + assertTrue((updateAssetMessage.content as MessageContent.Asset).value.remoteData.hasValidData()) + verify(arrangement.persistMessage) + .suspendFunction(arrangement.persistMessage::invoke) + .with(matching { + it.id == updateAssetMessage.id + && it.conversationId.toString() == updateAssetMessage.conversationId.toString() + && it.visibility == updateAssetMessage.visibility + }) + .wasInvoked(exactly = once) + + verify(arrangement.messageRepository) + .suspendFunction(arrangement.messageRepository::getMessageById) + .with(eq(previewAssetMessage.conversationId), eq(previewAssetMessage.id)) + .wasNotInvoked() + + verify(arrangement.validateAssetMimeType) + .suspendFunction(arrangement.validateAssetMimeType::invoke) + .with(any(), any>()) + .wasNotInvoked() + } + private class Arrangement { @Mock @@ -266,7 +382,7 @@ class AssetMessageHandlerTest { val userConfigRepository = mock(classOf()) @Mock - val validateAssetMimeType = mock(classOf()) + val validateAssetMimeType = mock(classOf()) private val assetMessageHandlerImpl = AssetMessageHandlerImpl(messageRepository, persistMessage, userConfigRepository, validateAssetMimeType)