diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt index 5d59ce5464..d0b4b87794 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt @@ -19,6 +19,7 @@ import io.element.android.features.messages.impl.attachments.preview.Attachments import io.element.android.features.messages.impl.attachments.preview.SendActionState import io.element.android.features.messages.impl.fixtures.aMediaAttachment import io.element.android.libraries.matrix.api.core.ProgressCallback +import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.room.FakeMatrixRoom @@ -35,6 +36,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test +import java.io.File class AttachmentsPreviewPresenterTest { @get:Rule @@ -45,7 +47,7 @@ class AttachmentsPreviewPresenterTest { @Test fun `present - send media success scenario`() = runTest { - val sendMediaResult = lambdaRecorder> { + val sendFileResult = lambdaRecorder> { _, _, _ -> Result.success(FakeMediaUploadHandler()) } val room = FakeMatrixRoom( @@ -54,7 +56,7 @@ class AttachmentsPreviewPresenterTest { Pair(5, 10), Pair(10, 10) ), - sendMediaResult = sendMediaResult, + sendFileResult = sendFileResult, ) val presenter = createAttachmentsPreviewPresenter(room = room) moleculeFlow(RecompositionMode.Immediate) { @@ -69,18 +71,18 @@ class AttachmentsPreviewPresenterTest { assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(1f)) val successState = awaitItem() assertThat(successState.sendActionState).isEqualTo(SendActionState.Done) - sendMediaResult.assertions().isCalledOnce() + sendFileResult.assertions().isCalledOnce() } } @Test fun `present - send media failure scenario`() = runTest { val failure = MediaPreProcessor.Failure(null) - val sendMediaResult = lambdaRecorder> { + val sendFileResult = lambdaRecorder> { _, _, _ -> Result.failure(failure) } val room = FakeMatrixRoom( - sendMediaResult = sendMediaResult, + sendFileResult = sendFileResult, ) val presenter = createAttachmentsPreviewPresenter(room = room) moleculeFlow(RecompositionMode.Immediate) { @@ -93,7 +95,7 @@ class AttachmentsPreviewPresenterTest { assertThat(loadingState.sendActionState).isEqualTo(SendActionState.Sending.Processing) val failureState = awaitItem() assertThat(failureState.sendActionState).isEqualTo(SendActionState.Failure(failure)) - sendMediaResult.assertions().isCalledOnce() + sendFileResult.assertions().isCalledOnce() failureState.eventSink(AttachmentsPreviewEvents.ClearSendState) val clearedState = awaitItem() assertThat(clearedState.sendActionState).isEqualTo(SendActionState.Idle) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt index b029cd724f..2e68c9b199 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt @@ -32,6 +32,7 @@ import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder @@ -684,7 +685,7 @@ class MessageComposerPresenterTest { @Test fun `present - Pick file from storage`() = runTest { - val sendMediaResult = lambdaRecorder { _: ProgressCallback? -> + val sendFileResult = lambdaRecorder> { _, _, _ -> Result.success(FakeMediaUploadHandler()) } val room = FakeMatrixRoom( @@ -693,7 +694,7 @@ class MessageComposerPresenterTest { Pair(5, 10), Pair(10, 10) ), - sendMediaResult = sendMediaResult, + sendFileResult = sendFileResult, typingNoticeResult = { Result.success(Unit) } ) val presenter = createPresenter(this, room = room) @@ -710,7 +711,7 @@ class MessageComposerPresenterTest { assertThat(awaitItem().attachmentsState).isEqualTo(AttachmentsState.Sending.Uploading(1f)) val sentState = awaitItem() assertThat(sentState.attachmentsState).isEqualTo(AttachmentsState.None) - sendMediaResult.assertions().isCalledOnce() + sendFileResult.assertions().isCalledOnce() } } @@ -852,8 +853,11 @@ class MessageComposerPresenterTest { @Test fun `present - Uploading media failure can be recovered from`() = runTest { + val sendFileResult = lambdaRecorder> { _, _, _ -> + Result.failure(Exception()) + } val room = FakeMatrixRoom( - sendMediaResult = { Result.failure(Exception()) }, + sendFileResult = sendFileResult, typingNoticeResult = { Result.success(Unit) } ) val presenter = createPresenter(this, room = room) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt index 0e0009e139..a7cbaaffd2 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt @@ -21,6 +21,7 @@ import io.element.android.features.messages.impl.messagecomposer.aReplyMode import io.element.android.features.messages.impl.voicemessages.VoiceMessageException import io.element.android.features.messages.test.FakeMessageComposerContext import io.element.android.libraries.matrix.api.core.ProgressCallback +import io.element.android.libraries.matrix.api.media.AudioInfo import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer @@ -46,6 +47,7 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test +import java.io.File import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -57,9 +59,12 @@ class VoiceMessageComposerPresenterTest { recordingDuration = RECORDING_DURATION ) private val analyticsService = FakeAnalyticsService() - private val sendMediaResult = lambdaRecorder> { Result.success(FakeMediaUploadHandler()) } + private val sendVoiceMessageResult = + lambdaRecorder, ProgressCallback?, Result> { _, _, _, _ -> + Result.success(FakeMediaUploadHandler()) + } private val matrixRoom = FakeMatrixRoom( - sendMediaResult = sendMediaResult + sendVoiceMessageResult = sendVoiceMessageResult ) private val mediaPreProcessor = FakeMediaPreProcessor().apply { givenAudioResult() } private val mediaSender = MediaSender(mediaPreProcessor, matrixRoom, InMemorySessionPreferencesStore()) @@ -292,7 +297,7 @@ class VoiceMessageComposerPresenterTest { val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) - sendMediaResult.assertions().isCalledOnce() + sendVoiceMessageResult.assertions().isCalledOnce() voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1) testPauseAndDestroy(finalState) @@ -343,7 +348,7 @@ class VoiceMessageComposerPresenterTest { val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) - sendMediaResult.assertions().isCalledOnce() + sendVoiceMessageResult.assertions().isCalledOnce() voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1) testPauseAndDestroy(finalState) @@ -366,7 +371,7 @@ class VoiceMessageComposerPresenterTest { val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) - sendMediaResult.assertions().isCalledOnce() + sendVoiceMessageResult.assertions().isCalledOnce() voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1) testPauseAndDestroy(finalState) @@ -390,7 +395,7 @@ class VoiceMessageComposerPresenterTest { val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(aPreviewState(isSending = true)) - sendMediaResult.assertions().isNeverCalled() + sendVoiceMessageResult.assertions().isNeverCalled() assertThat(analyticsService.trackedErrors).hasSize(0) voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 0) @@ -415,13 +420,13 @@ class VoiceMessageComposerPresenterTest { ensureAllEventsConsumed() assertThat(previewState.voiceMessageState).isEqualTo(aPreviewState()) - sendMediaResult.assertions().isNeverCalled() + sendVoiceMessageResult.assertions().isNeverCalled() mediaPreProcessor.givenAudioResult() previewState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage) val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) - sendMediaResult.assertions().isCalledOnce() + sendVoiceMessageResult.assertions().isCalledOnce() voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1) testPauseAndDestroy(finalState) @@ -458,7 +463,7 @@ class VoiceMessageComposerPresenterTest { assertThat(showSendFailureDialog).isFalse() } - sendMediaResult.assertions().isNeverCalled() + sendVoiceMessageResult.assertions().isNeverCalled() testPauseAndDestroy(finalState) } } @@ -474,7 +479,7 @@ class VoiceMessageComposerPresenterTest { initialState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage) assertThat(initialState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) - sendMediaResult.assertions().isNeverCalled() + sendVoiceMessageResult.assertions().isNeverCalled() assertThat(analyticsService.trackedErrors).hasSize(1) voiceRecorder.assertCalls(started = 0) @@ -493,7 +498,7 @@ class VoiceMessageComposerPresenterTest { val initialState = awaitItem() initialState.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - sendMediaResult.assertions().isNeverCalled() + sendVoiceMessageResult.assertions().isNeverCalled() assertThat(analyticsService.trackedErrors).containsExactly( VoiceMessageException.PermissionMissing(message = "Expected permission to record but none", cause = exception) ) diff --git a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt index a834baf2bc..10d42716a9 100644 --- a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt +++ b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt @@ -16,6 +16,8 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.ProgressCallback +import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.FakeMatrixClient @@ -25,12 +27,14 @@ import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.lambdaRecorder import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner +import java.io.File @RunWith(RobolectricTestRunner::class) class SharePresenterTest { @@ -112,8 +116,11 @@ class SharePresenterTest { @Test fun `present - send media ok`() = runTest { + val sendFileResult = lambdaRecorder> { _, _, _ -> + Result.success(FakeMediaUploadHandler()) + } val matrixRoom = FakeMatrixRoom( - sendMediaResult = { Result.success(FakeMediaUploadHandler()) }, + sendFileResult = sendFileResult, ) val matrixClient = FakeMatrixClient().apply { givenGetRoomResult(A_ROOM_ID, matrixRoom) @@ -141,6 +148,7 @@ class SharePresenterTest { val success = awaitItem() assertThat(success.shareAction.isSuccess()).isTrue() assertThat(success.shareAction).isEqualTo(AsyncAction.Success(listOf(A_ROOM_ID))) + sendFileResult.assertions().isCalledOnce() } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index b46b45f20e..24e253311d 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -87,7 +87,16 @@ class FakeMatrixRoom( private val canRedactOtherResult: (UserId) -> Result = { lambdaError() }, private val canSendStateResult: (UserId, StateEventType) -> Result = { _, _ -> lambdaError() }, private val canUserSendMessageResult: (UserId, MessageEventType) -> Result = { _, _ -> lambdaError() }, - private val sendMediaResult: (ProgressCallback?) -> Result = { lambdaError() }, + private val sendImageResult: (File, File?, ImageInfo, String?, String?, ProgressCallback?) -> Result = + { _, _, _, _, _, _ -> lambdaError() }, + private val sendVideoResult: (File, File?, VideoInfo, String?, String?, ProgressCallback?) -> Result = + { _, _, _, _, _, _ -> lambdaError() }, + private val sendFileResult: (File, FileInfo, ProgressCallback?) -> Result = + { _, _, _ -> lambdaError() }, + private val sendAudioResult: (File, AudioInfo, ProgressCallback?) -> Result = + { _, _, _ -> lambdaError() }, + private val sendVoiceMessageResult: (File, AudioInfo, List, ProgressCallback?) -> Result = + { _, _, _, _ -> lambdaError() }, private val setNameResult: (String) -> Result = { lambdaError() }, private val setTopicResult: (String) -> Result = { lambdaError() }, private val updateAvatarResult: (String, ByteArray) -> Result = { _, _ -> lambdaError() }, @@ -315,7 +324,17 @@ class FakeMatrixRoom( body: String?, formattedBody: String?, progressCallback: ProgressCallback? - ): Result = fakeSendMedia(progressCallback) + ): Result = simulateLongTask { + simulateSendMediaProgress(progressCallback) + sendImageResult( + file, + thumbnailFile, + imageInfo, + body, + formattedBody, + progressCallback, + ) + } override suspend fun sendVideo( file: File, @@ -324,32 +343,53 @@ class FakeMatrixRoom( body: String?, formattedBody: String?, progressCallback: ProgressCallback? - ): Result = fakeSendMedia( - progressCallback - ) + ): Result = simulateLongTask { + simulateSendMediaProgress(progressCallback) + sendVideoResult( + file, + thumbnailFile, + videoInfo, + body, + formattedBody, + progressCallback, + ) + } override suspend fun sendAudio( file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback? - ): Result = fakeSendMedia(progressCallback) + ): Result = simulateLongTask { + simulateSendMediaProgress(progressCallback) + sendAudioResult( + file, + audioInfo, + progressCallback, + ) + } override suspend fun sendFile( file: File, fileInfo: FileInfo, progressCallback: ProgressCallback? - ): Result = fakeSendMedia(progressCallback) - - override suspend fun forwardEvent(eventId: EventId, roomIds: List): Result = simulateLongTask { - forwardEventResult(eventId, roomIds) + ): Result = simulateLongTask { + simulateSendMediaProgress(progressCallback) + sendFileResult( + file, + fileInfo, + progressCallback, + ) } - private suspend fun fakeSendMedia(progressCallback: ProgressCallback?): Result = simulateLongTask { + private suspend fun simulateSendMediaProgress(progressCallback: ProgressCallback?) { progressCallbackValues.forEach { (current, total) -> progressCallback?.onProgress(current, total) delay(1) } - sendMediaResult(progressCallback) + } + + override suspend fun forwardEvent(eventId: EventId, roomIds: List): Result = simulateLongTask { + forwardEventResult(eventId, roomIds) } override suspend fun updateAvatar(mimeType: String, data: ByteArray): Result = simulateLongTask { @@ -472,7 +512,15 @@ class FakeMatrixRoom( audioInfo: AudioInfo, waveform: List, progressCallback: ProgressCallback? - ): Result = fakeSendMedia(progressCallback) + ): Result = simulateLongTask { + simulateSendMediaProgress(progressCallback) + sendVoiceMessageResult( + file, + audioInfo, + waveform, + progressCallback, + ) + } override suspend fun typingNotice(isTyping: Boolean): Result { return typingNoticeResult(isTyping) diff --git a/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt b/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt index 7724855cc3..1cfb46c20e 100644 --- a/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt +++ b/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt @@ -11,6 +11,8 @@ import android.net.Uri import com.google.common.truth.Truth.assertThat import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.core.ProgressCallback +import io.element.android.libraries.matrix.api.media.FileInfo +import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.room.FakeMatrixRoom @@ -26,13 +28,14 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner +import java.io.File @RunWith(RobolectricTestRunner::class) class MediaSenderTest { @Test fun `given an attachment when sending it the preprocessor always runs`() = runTest { val preProcessor = FakeMediaPreProcessor() - val sender = aMediaSender(preProcessor) + val sender = createMediaSender(preProcessor) val uri = Uri.parse("content://image.jpg") sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg) @@ -42,17 +45,17 @@ class MediaSenderTest { @Test fun `given an attachment when sending it the MatrixRoom will call sendMedia`() = runTest { - val sendMediaResult = lambdaRecorder> { - Result.success(FakeMediaUploadHandler()) - } + val sendImageResult = + lambdaRecorder> { _, _, _, _, _, _ -> + Result.success(FakeMediaUploadHandler()) + } val room = FakeMatrixRoom( - sendMediaResult = sendMediaResult + sendImageResult = sendImageResult ) - val sender = aMediaSender(room = room) + val sender = createMediaSender(room = room) val uri = Uri.parse("content://image.jpg") sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg) - sendMediaResult.assertions().isCalledOnce() } @Test @@ -60,7 +63,7 @@ class MediaSenderTest { val preProcessor = FakeMediaPreProcessor().apply { givenResult(Result.failure(Exception())) } - val sender = aMediaSender(preProcessor) + val sender = createMediaSender(preProcessor) val uri = Uri.parse("content://image.jpg") val result = sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg) @@ -70,10 +73,14 @@ class MediaSenderTest { @Test fun `given a failure in the media upload when sending the whole process fails`() = runTest { + val sendImageResult = + lambdaRecorder> { _, _, _, _, _, _ -> + Result.failure(Exception()) + } val room = FakeMatrixRoom( - sendMediaResult = { Result.failure(Exception()) } + sendImageResult = sendImageResult ) - val sender = aMediaSender(room = room) + val sender = createMediaSender(room = room) val uri = Uri.parse("content://image.jpg") val result = sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg) @@ -84,10 +91,13 @@ class MediaSenderTest { @OptIn(ExperimentalCoroutinesApi::class) @Test fun `given a cancellation in the media upload when sending the job is cancelled`() = runTest(StandardTestDispatcher()) { + val sendFileResult = lambdaRecorder> { _, _, _ -> + Result.success(FakeMediaUploadHandler()) + } val room = FakeMatrixRoom( - sendMediaResult = { Result.success(FakeMediaUploadHandler()) } + sendFileResult = sendFileResult ) - val sender = aMediaSender(room = room) + val sender = createMediaSender(room = room) val sendJob = launch { val uri = Uri.parse("content://image.jpg") sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg) @@ -106,9 +116,10 @@ class MediaSenderTest { // Assert the file is not being uploaded anymore assertThat(sender.hasOngoingMediaUploads).isFalse() + sendFileResult.assertions().isCalledOnce() } - private fun aMediaSender( + private fun createMediaSender( preProcessor: MediaPreProcessor = FakeMediaPreProcessor(), room: MatrixRoom = FakeMatrixRoom(), sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),