From 3dd320193c103704530fe4fc2d39ed646bcc2a9a Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 9 Mar 2026 02:24:36 +0300 Subject: [PATCH] android: add media repository tests and checklist updates --- android/CHANGELOG.md | 4 + .../repository/NetworkMediaRepositoryTest.kt | 113 ++++++++++++++++++ .../NetworkMessageRepositoryTest.kt | 14 +++ docs/android-checklist.md | 2 +- 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 android/app/src/test/java/ru/daemonlord/messenger/data/media/repository/NetworkMediaRepositoryTest.kt diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index f77b48c..00c06ac 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -104,3 +104,7 @@ - Extended `MessageRepository` with media send flow (`sendMediaMessage`) and optimistic local update behavior. - Wired media API/repository through Hilt modules. - Integrated file picking and media sending into Android `ChatScreen`/`ChatViewModel` with upload state handling. + +### Step 17 - Sprint B / media tests +- Added `NetworkMediaRepositoryTest` for successful upload+attach flow. +- Added error-path coverage for failed presigned upload handling. diff --git a/android/app/src/test/java/ru/daemonlord/messenger/data/media/repository/NetworkMediaRepositoryTest.kt b/android/app/src/test/java/ru/daemonlord/messenger/data/media/repository/NetworkMediaRepositoryTest.kt new file mode 100644 index 0000000..ab19657 --- /dev/null +++ b/android/app/src/test/java/ru/daemonlord/messenger/data/media/repository/NetworkMediaRepositoryTest.kt @@ -0,0 +1,113 @@ +package ru.daemonlord.messenger.data.media.repository + +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.After +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import retrofit2.Retrofit +import ru.daemonlord.messenger.data.media.api.MediaApiService +import ru.daemonlord.messenger.domain.common.AppResult + +@OptIn(ExperimentalCoroutinesApi::class) +class NetworkMediaRepositoryTest { + + private lateinit var apiServer: MockWebServer + private lateinit var uploadServer: MockWebServer + private lateinit var mediaApiService: MediaApiService + private val dispatcher = StandardTestDispatcher() + + @Before + fun setUp() { + apiServer = MockWebServer().also { it.start() } + uploadServer = MockWebServer().also { it.start() } + mediaApiService = Retrofit.Builder() + .baseUrl(apiServer.url("/")) + .addConverterFactory( + Json { ignoreUnknownKeys = true }.asConverterFactory("application/json".toMediaType()) + ) + .build() + .create(MediaApiService::class.java) + } + + @After + fun tearDown() { + apiServer.shutdown() + uploadServer.shutdown() + } + + @Test + fun uploadAndAttach_success() = runTest(dispatcher) { + apiServer.enqueue( + MockResponse().setResponseCode(200).setBody( + """ + { + "upload_url": "${uploadServer.url("/upload")}", + "file_url": "https://s3.daemonlord.ru/uploads/image.png", + "object_key": "uploads/image.png", + "expires_in": 900, + "required_headers": { "Content-Type": "image/png" } + } + """.trimIndent() + ) + ) + uploadServer.enqueue(MockResponse().setResponseCode(200)) + apiServer.enqueue(MockResponse().setResponseCode(200).setBody("{}")) + + val repository = NetworkMediaRepository( + mediaApiService = mediaApiService, + uploadClient = OkHttpClient(), + ioDispatcher = dispatcher, + ) + + val result = repository.uploadAndAttach( + messageId = 10, + fileName = "image.png", + mimeType = "image/png", + bytes = byteArrayOf(1, 2, 3), + ) + + assertTrue(result is AppResult.Success) + } + + @Test + fun uploadAndAttach_uploadFails_returnsError() = runTest(dispatcher) { + apiServer.enqueue( + MockResponse().setResponseCode(200).setBody( + """ + { + "upload_url": "${uploadServer.url("/upload")}", + "file_url": "https://s3.daemonlord.ru/uploads/image.png", + "object_key": "uploads/image.png", + "expires_in": 900, + "required_headers": { "Content-Type": "image/png" } + } + """.trimIndent() + ) + ) + uploadServer.enqueue(MockResponse().setResponseCode(500)) + + val repository = NetworkMediaRepository( + mediaApiService = mediaApiService, + uploadClient = OkHttpClient(), + ioDispatcher = dispatcher, + ) + + val result = repository.uploadAndAttach( + messageId = 10, + fileName = "image.png", + mimeType = "image/png", + bytes = byteArrayOf(1, 2, 3), + ) + + assertTrue(result is AppResult.Error) + } +} diff --git a/android/app/src/test/java/ru/daemonlord/messenger/data/message/repository/NetworkMessageRepositoryTest.kt b/android/app/src/test/java/ru/daemonlord/messenger/data/message/repository/NetworkMessageRepositoryTest.kt index 857fd35..ab8e0c9 100644 --- a/android/app/src/test/java/ru/daemonlord/messenger/data/message/repository/NetworkMessageRepositoryTest.kt +++ b/android/app/src/test/java/ru/daemonlord/messenger/data/message/repository/NetworkMessageRepositoryTest.kt @@ -18,6 +18,8 @@ import ru.daemonlord.messenger.data.message.api.MessageApiService import ru.daemonlord.messenger.data.message.dto.MessageCreateRequestDto import ru.daemonlord.messenger.data.message.dto.MessageReadDto import ru.daemonlord.messenger.data.message.dto.MessageUpdateRequestDto +import ru.daemonlord.messenger.domain.common.AppResult +import ru.daemonlord.messenger.domain.media.repository.MediaRepository @RunWith(RobolectricTestRunner::class) @OptIn(ExperimentalCoroutinesApi::class) @@ -92,6 +94,7 @@ class NetworkMessageRepositoryTest { messageApiService = fakeApi, messageDao = db.messageDao(), chatDao = db.chatDao(), + mediaRepository = FakeMediaRepository(), ioDispatcher = dispatcher, ) } @@ -121,4 +124,15 @@ class NetworkMessageRepositoryTest { override suspend fun deleteMessage(messageId: Long, forAll: Boolean) = Unit } + + private class FakeMediaRepository : MediaRepository { + override suspend fun uploadAndAttach( + messageId: Long, + fileName: String, + mimeType: String, + bytes: ByteArray, + ): AppResult { + return AppResult.Success(Unit) + } + } } diff --git a/docs/android-checklist.md b/docs/android-checklist.md index 3d1684e..d60f8a8 100644 --- a/docs/android-checklist.md +++ b/docs/android-checklist.md @@ -58,7 +58,7 @@ - [ ] Delivery/read states ## 8. Медиа и вложения -- [ ] Upload image/video/file/audio +- [x] Upload image/video/file/audio - [ ] Галерея в сообщении (multi media) - [ ] Media viewer (zoom/swipe/download) - [ ] Единое контекстное меню для медиа