android: add media repository tests and checklist updates
Some checks failed
CI / test (push) Failing after 2m3s
Some checks failed
CI / test (push) Failing after 2m3s
This commit is contained in:
@@ -104,3 +104,7 @@
|
|||||||
- Extended `MessageRepository` with media send flow (`sendMediaMessage`) and optimistic local update behavior.
|
- Extended `MessageRepository` with media send flow (`sendMediaMessage`) and optimistic local update behavior.
|
||||||
- Wired media API/repository through Hilt modules.
|
- Wired media API/repository through Hilt modules.
|
||||||
- Integrated file picking and media sending into Android `ChatScreen`/`ChatViewModel` with upload state handling.
|
- 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.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.MessageCreateRequestDto
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageReadDto
|
import ru.daemonlord.messenger.data.message.dto.MessageReadDto
|
||||||
import ru.daemonlord.messenger.data.message.dto.MessageUpdateRequestDto
|
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)
|
@RunWith(RobolectricTestRunner::class)
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@@ -92,6 +94,7 @@ class NetworkMessageRepositoryTest {
|
|||||||
messageApiService = fakeApi,
|
messageApiService = fakeApi,
|
||||||
messageDao = db.messageDao(),
|
messageDao = db.messageDao(),
|
||||||
chatDao = db.chatDao(),
|
chatDao = db.chatDao(),
|
||||||
|
mediaRepository = FakeMediaRepository(),
|
||||||
ioDispatcher = dispatcher,
|
ioDispatcher = dispatcher,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -121,4 +124,15 @@ class NetworkMessageRepositoryTest {
|
|||||||
|
|
||||||
override suspend fun deleteMessage(messageId: Long, forAll: Boolean) = Unit
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
- [ ] Delivery/read states
|
- [ ] Delivery/read states
|
||||||
|
|
||||||
## 8. Медиа и вложения
|
## 8. Медиа и вложения
|
||||||
- [ ] Upload image/video/file/audio
|
- [x] Upload image/video/file/audio
|
||||||
- [ ] Галерея в сообщении (multi media)
|
- [ ] Галерея в сообщении (multi media)
|
||||||
- [ ] Media viewer (zoom/swipe/download)
|
- [ ] Media viewer (zoom/swipe/download)
|
||||||
- [ ] Единое контекстное меню для медиа
|
- [ ] Единое контекстное меню для медиа
|
||||||
|
|||||||
Reference in New Issue
Block a user