android: add media repository tests and checklist updates
Some checks failed
CI / test (push) Failing after 2m3s

This commit is contained in:
Codex
2026-03-09 02:24:36 +03:00
parent 8d13eb104e
commit 3dd320193c
4 changed files with 132 additions and 1 deletions

View File

@@ -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.

View File

@@ -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)
}
}

View File

@@ -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<Unit> {
return AppResult.Success(Unit)
}
}
}