feat(update): Update-Check & APK-Download Data/Domain-Layer
VersionInfo-Datenmodell, UpdateRepository (checkForUpdate + downloadApk mit Progress-Callback) und CheckForUpdateUseCase implementiert. Neue Dateien: - domain/model/VersionInfo.kt: @Serializable Datenmodell (versionCode, versionName, apkUrl) - domain/model/UpdateCheckResult.kt: Sealed interface (UpdateAvailable, UpToDate, Error, NotConfigured) - domain/repository/UpdateRepository.kt: Interface - data/repository/UpdateRepositoryImpl.kt: Ktor HttpClient mit Streaming-Download und Progress-Reporting - domain/usecase/CheckForUpdateUseCase.kt: Vergleicht Server-versionCode mit BuildConfig.VERSION_CODE, prueft Server-URL via SettingsRepository Geaenderte Dateien: - di/RepositoryModule.kt: UpdateRepository Hilt-Binding ergaenzt Tests (12 neue): - UpdateRepositoryImplTest: checkForUpdate (Erfolg, 500, trailing slash), downloadApk (Erfolg+Progress, 404, Parent-Dir-Erstellung) - CheckForUpdateUseCaseTest: neuer/gleicher/aelterer versionCode, leere/blanke Server-URL, Netzwerkfehler Closes #84
This commit is contained in:
parent
994d6b1b07
commit
3ce8ec28e9
8 changed files with 465 additions and 0 deletions
|
|
@ -0,0 +1,88 @@
|
||||||
|
package de.krisenvorrat.app.data.repository
|
||||||
|
|
||||||
|
import de.krisenvorrat.app.domain.model.VersionInfo
|
||||||
|
import de.krisenvorrat.app.domain.repository.UpdateRepository
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.call.body
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.client.request.prepareGet
|
||||||
|
import io.ktor.client.statement.bodyAsChannel
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.http.contentLength
|
||||||
|
import io.ktor.utils.io.readAvailable
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
import java.net.ConnectException
|
||||||
|
import java.net.SocketTimeoutException
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class UpdateRepositoryImpl @Inject constructor(
|
||||||
|
private val httpClient: HttpClient
|
||||||
|
) : UpdateRepository {
|
||||||
|
|
||||||
|
override suspend fun checkForUpdate(serverUrl: String): Result<VersionInfo> =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val url = "${serverUrl.trimEnd('/')}/api/version"
|
||||||
|
val response = httpClient.get(url)
|
||||||
|
when (response.status) {
|
||||||
|
HttpStatusCode.OK -> Result.success(response.body<VersionInfo>())
|
||||||
|
else -> Result.failure(
|
||||||
|
Exception("Server returned ${response.status.value}: ${response.status.description}")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: SocketTimeoutException) {
|
||||||
|
Result.failure(e)
|
||||||
|
} catch (e: ConnectException) {
|
||||||
|
Result.failure(e)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun downloadApk(
|
||||||
|
apkUrl: String,
|
||||||
|
targetFile: File,
|
||||||
|
onProgress: (Float) -> Unit
|
||||||
|
): Result<File> = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
targetFile.parentFile?.mkdirs()
|
||||||
|
|
||||||
|
httpClient.prepareGet(apkUrl).execute { response ->
|
||||||
|
if (response.status != HttpStatusCode.OK) {
|
||||||
|
return@execute Result.failure(
|
||||||
|
Exception("Download failed: ${response.status.value}")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val totalBytes = response.contentLength() ?: -1L
|
||||||
|
val channel = response.bodyAsChannel()
|
||||||
|
val buffer = ByteArray(DOWNLOAD_BUFFER_SIZE)
|
||||||
|
var bytesRead = 0L
|
||||||
|
|
||||||
|
targetFile.outputStream().buffered().use { output ->
|
||||||
|
while (!channel.isClosedForRead) {
|
||||||
|
val read = channel.readAvailable(buffer)
|
||||||
|
if (read <= 0) break
|
||||||
|
output.write(buffer, 0, read)
|
||||||
|
bytesRead += read
|
||||||
|
if (totalBytes > 0) {
|
||||||
|
onProgress(bytesRead.toFloat() / totalBytes.toFloat())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onProgress(1f)
|
||||||
|
Result.success(targetFile)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
targetFile.delete()
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val DOWNLOAD_BUFFER_SIZE = 8192
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,12 +10,14 @@ import de.krisenvorrat.app.data.repository.ItemRepositoryImpl
|
||||||
import de.krisenvorrat.app.data.repository.LocationRepositoryImpl
|
import de.krisenvorrat.app.data.repository.LocationRepositoryImpl
|
||||||
import de.krisenvorrat.app.data.repository.MessageRepositoryImpl
|
import de.krisenvorrat.app.data.repository.MessageRepositoryImpl
|
||||||
import de.krisenvorrat.app.data.repository.SettingsRepositoryImpl
|
import de.krisenvorrat.app.data.repository.SettingsRepositoryImpl
|
||||||
|
import de.krisenvorrat.app.data.repository.UpdateRepositoryImpl
|
||||||
import de.krisenvorrat.app.domain.repository.CategoryRepository
|
import de.krisenvorrat.app.domain.repository.CategoryRepository
|
||||||
import de.krisenvorrat.app.domain.repository.ImportExportRepository
|
import de.krisenvorrat.app.domain.repository.ImportExportRepository
|
||||||
import de.krisenvorrat.app.domain.repository.ItemRepository
|
import de.krisenvorrat.app.domain.repository.ItemRepository
|
||||||
import de.krisenvorrat.app.domain.repository.LocationRepository
|
import de.krisenvorrat.app.domain.repository.LocationRepository
|
||||||
import de.krisenvorrat.app.domain.repository.MessageRepository
|
import de.krisenvorrat.app.domain.repository.MessageRepository
|
||||||
import de.krisenvorrat.app.domain.repository.SettingsRepository
|
import de.krisenvorrat.app.domain.repository.SettingsRepository
|
||||||
|
import de.krisenvorrat.app.domain.repository.UpdateRepository
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
|
@ -45,4 +47,8 @@ internal abstract class RepositoryModule {
|
||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
@Singleton
|
||||||
abstract fun bindMessageRepository(impl: MessageRepositoryImpl): MessageRepository
|
abstract fun bindMessageRepository(impl: MessageRepositoryImpl): MessageRepository
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
abstract fun bindUpdateRepository(impl: UpdateRepositoryImpl): UpdateRepository
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package de.krisenvorrat.app.domain.model
|
||||||
|
|
||||||
|
internal sealed interface UpdateCheckResult {
|
||||||
|
data class UpdateAvailable(val versionInfo: VersionInfo) : UpdateCheckResult
|
||||||
|
data object UpToDate : UpdateCheckResult
|
||||||
|
data class Error(val cause: Throwable) : UpdateCheckResult
|
||||||
|
data object NotConfigured : UpdateCheckResult
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package de.krisenvorrat.app.domain.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
internal data class VersionInfo(
|
||||||
|
val versionCode: Int,
|
||||||
|
val versionName: String,
|
||||||
|
val apkUrl: String
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package de.krisenvorrat.app.domain.repository
|
||||||
|
|
||||||
|
import de.krisenvorrat.app.domain.model.VersionInfo
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
internal interface UpdateRepository {
|
||||||
|
suspend fun checkForUpdate(serverUrl: String): Result<VersionInfo>
|
||||||
|
suspend fun downloadApk(apkUrl: String, targetFile: File, onProgress: (Float) -> Unit): Result<File>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package de.krisenvorrat.app.domain.usecase
|
||||||
|
|
||||||
|
import de.krisenvorrat.app.domain.model.SettingsKey.StringKey
|
||||||
|
import de.krisenvorrat.app.domain.model.UpdateCheckResult
|
||||||
|
import de.krisenvorrat.app.domain.repository.SettingsRepository
|
||||||
|
import de.krisenvorrat.app.domain.repository.UpdateRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class CheckForUpdateUseCase @Inject constructor(
|
||||||
|
private val updateRepository: UpdateRepository,
|
||||||
|
private val settingsRepository: SettingsRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(currentVersionCode: Int): UpdateCheckResult {
|
||||||
|
val serverUrl = settingsRepository.getString(StringKey.ServerUrl)
|
||||||
|
if (serverUrl.isBlank()) {
|
||||||
|
return UpdateCheckResult.NotConfigured
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateRepository.checkForUpdate(serverUrl).fold(
|
||||||
|
onSuccess = { versionInfo ->
|
||||||
|
if (versionInfo.versionCode > currentVersionCode) {
|
||||||
|
UpdateCheckResult.UpdateAvailable(versionInfo)
|
||||||
|
} else {
|
||||||
|
UpdateCheckResult.UpToDate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = { error ->
|
||||||
|
UpdateCheckResult.Error(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
package de.krisenvorrat.app.data.repository
|
||||||
|
|
||||||
|
import de.krisenvorrat.app.domain.model.VersionInfo
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.engine.mock.MockEngine
|
||||||
|
import io.ktor.client.engine.mock.respond
|
||||||
|
import io.ktor.client.engine.mock.respondError
|
||||||
|
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||||
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.http.HttpHeaders
|
||||||
|
import io.ktor.http.HttpMethod
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.http.headersOf
|
||||||
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class UpdateRepositoryImplTest {
|
||||||
|
|
||||||
|
private val jsonSerializer = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
encodeDefaults = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var httpClient: HttpClient
|
||||||
|
private lateinit var repository: UpdateRepositoryImpl
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
if (::httpClient.isInitialized) {
|
||||||
|
httpClient.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createClient(engine: MockEngine): HttpClient = HttpClient(engine) {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(jsonSerializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- checkForUpdate ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_checkForUpdate_serverReturnsVersionInfo_returnsSuccess() = runTest {
|
||||||
|
// Given
|
||||||
|
val versionInfo = VersionInfo(versionCode = 5, versionName = "2.0", apkUrl = "https://example.com/app.apk")
|
||||||
|
val engine = MockEngine { request ->
|
||||||
|
assertEquals("/api/version", request.url.encodedPath)
|
||||||
|
assertEquals(HttpMethod.Get, request.method)
|
||||||
|
respond(
|
||||||
|
content = jsonSerializer.encodeToString(VersionInfo.serializer(), versionInfo),
|
||||||
|
status = HttpStatusCode.OK,
|
||||||
|
headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
httpClient = createClient(engine)
|
||||||
|
repository = UpdateRepositoryImpl(httpClient)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = repository.checkForUpdate("https://example.com")
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result.isSuccess)
|
||||||
|
assertEquals(versionInfo, result.getOrNull())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_checkForUpdate_serverReturns500_returnsFailure() = runTest {
|
||||||
|
// Given
|
||||||
|
val engine = MockEngine {
|
||||||
|
respondError(HttpStatusCode.InternalServerError)
|
||||||
|
}
|
||||||
|
httpClient = createClient(engine)
|
||||||
|
repository = UpdateRepositoryImpl(httpClient)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = repository.checkForUpdate("https://example.com")
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result.isFailure)
|
||||||
|
assertTrue(result.exceptionOrNull()?.message?.contains("500") == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_checkForUpdate_serverUrlWithTrailingSlash_stripsSlash() = runTest {
|
||||||
|
// Given
|
||||||
|
val versionInfo = VersionInfo(versionCode = 5, versionName = "2.0", apkUrl = "https://example.com/app.apk")
|
||||||
|
val engine = MockEngine { request ->
|
||||||
|
assertEquals("/api/version", request.url.encodedPath)
|
||||||
|
respond(
|
||||||
|
content = jsonSerializer.encodeToString(VersionInfo.serializer(), versionInfo),
|
||||||
|
status = HttpStatusCode.OK,
|
||||||
|
headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
httpClient = createClient(engine)
|
||||||
|
repository = UpdateRepositoryImpl(httpClient)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = repository.checkForUpdate("https://example.com/")
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result.isSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- downloadApk ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_downloadApk_serverReturnsData_writesFileAndReportsProgress() = runTest {
|
||||||
|
// Given
|
||||||
|
val apkData = ByteArray(1024) { it.toByte() }
|
||||||
|
val engine = MockEngine {
|
||||||
|
respond(
|
||||||
|
content = apkData,
|
||||||
|
status = HttpStatusCode.OK,
|
||||||
|
headers = headersOf(HttpHeaders.ContentLength, apkData.size.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
httpClient = createClient(engine)
|
||||||
|
repository = UpdateRepositoryImpl(httpClient)
|
||||||
|
|
||||||
|
val targetFile = File.createTempFile("test-apk", ".apk")
|
||||||
|
targetFile.deleteOnExit()
|
||||||
|
val progressValues = mutableListOf<Float>()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = repository.downloadApk(
|
||||||
|
apkUrl = "https://example.com/app.apk",
|
||||||
|
targetFile = targetFile,
|
||||||
|
onProgress = { progressValues.add(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result.isSuccess)
|
||||||
|
assertEquals(targetFile, result.getOrNull())
|
||||||
|
assertTrue(targetFile.exists())
|
||||||
|
assertEquals(apkData.size.toLong(), targetFile.length())
|
||||||
|
assertTrue(progressValues.isNotEmpty())
|
||||||
|
assertEquals(1f, progressValues.last(), 0.001f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_downloadApk_serverReturns404_returnsFailure() = runTest {
|
||||||
|
// Given
|
||||||
|
val engine = MockEngine {
|
||||||
|
respondError(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
httpClient = createClient(engine)
|
||||||
|
repository = UpdateRepositoryImpl(httpClient)
|
||||||
|
|
||||||
|
val targetFile = File.createTempFile("test-apk", ".apk")
|
||||||
|
targetFile.deleteOnExit()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = repository.downloadApk(
|
||||||
|
apkUrl = "https://example.com/app.apk",
|
||||||
|
targetFile = targetFile,
|
||||||
|
onProgress = {}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result.isFailure)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_downloadApk_createsParentDirectories() = runTest {
|
||||||
|
// Given
|
||||||
|
val apkData = ByteArray(64) { it.toByte() }
|
||||||
|
val engine = MockEngine {
|
||||||
|
respond(
|
||||||
|
content = apkData,
|
||||||
|
status = HttpStatusCode.OK,
|
||||||
|
headers = headersOf(HttpHeaders.ContentLength, apkData.size.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
httpClient = createClient(engine)
|
||||||
|
repository = UpdateRepositoryImpl(httpClient)
|
||||||
|
|
||||||
|
val tempDir = File(System.getProperty("java.io.tmpdir"), "test-update-${System.nanoTime()}")
|
||||||
|
val targetFile = File(tempDir, "subdir/app-latest.apk")
|
||||||
|
|
||||||
|
try {
|
||||||
|
// When
|
||||||
|
val result = repository.downloadApk(
|
||||||
|
apkUrl = "https://example.com/app.apk",
|
||||||
|
targetFile = targetFile,
|
||||||
|
onProgress = {}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result.isSuccess)
|
||||||
|
assertTrue(targetFile.exists())
|
||||||
|
} finally {
|
||||||
|
tempDir.deleteRecursively()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
package de.krisenvorrat.app.domain.usecase
|
||||||
|
|
||||||
|
import de.krisenvorrat.app.domain.model.SettingsKey.StringKey
|
||||||
|
import de.krisenvorrat.app.domain.model.UpdateCheckResult
|
||||||
|
import de.krisenvorrat.app.domain.model.VersionInfo
|
||||||
|
import de.krisenvorrat.app.domain.repository.SettingsRepository
|
||||||
|
import de.krisenvorrat.app.domain.repository.UpdateRepository
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.mockk
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import java.net.ConnectException
|
||||||
|
|
||||||
|
class CheckForUpdateUseCaseTest {
|
||||||
|
|
||||||
|
private val updateRepository: UpdateRepository = mockk()
|
||||||
|
private val settingsRepository: SettingsRepository = mockk()
|
||||||
|
private val useCase = CheckForUpdateUseCase(updateRepository, settingsRepository)
|
||||||
|
|
||||||
|
private fun setupServerUrl(url: String) {
|
||||||
|
coEvery { settingsRepository.getString(StringKey.ServerUrl) } returns url
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_invoke_serverHasNewerVersion_returnsUpdateAvailable() = runTest {
|
||||||
|
// Given
|
||||||
|
val versionInfo = VersionInfo(versionCode = 5, versionName = "2.0", apkUrl = "https://example.com/app.apk")
|
||||||
|
setupServerUrl("https://example.com")
|
||||||
|
coEvery { updateRepository.checkForUpdate("https://example.com") } returns Result.success(versionInfo)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = useCase(currentVersionCode = 3)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result is UpdateCheckResult.UpdateAvailable)
|
||||||
|
assertEquals(versionInfo, (result as UpdateCheckResult.UpdateAvailable).versionInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_invoke_serverHasSameVersion_returnsUpToDate() = runTest {
|
||||||
|
// Given
|
||||||
|
val versionInfo = VersionInfo(versionCode = 3, versionName = "1.2", apkUrl = "https://example.com/app.apk")
|
||||||
|
setupServerUrl("https://example.com")
|
||||||
|
coEvery { updateRepository.checkForUpdate("https://example.com") } returns Result.success(versionInfo)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = useCase(currentVersionCode = 3)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result is UpdateCheckResult.UpToDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_invoke_serverHasOlderVersion_returnsUpToDate() = runTest {
|
||||||
|
// Given
|
||||||
|
val versionInfo = VersionInfo(versionCode = 2, versionName = "1.0", apkUrl = "https://example.com/app.apk")
|
||||||
|
setupServerUrl("https://example.com")
|
||||||
|
coEvery { updateRepository.checkForUpdate("https://example.com") } returns Result.success(versionInfo)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = useCase(currentVersionCode = 3)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result is UpdateCheckResult.UpToDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_invoke_serverUrlIsEmpty_returnsNotConfigured() = runTest {
|
||||||
|
// Given
|
||||||
|
setupServerUrl("")
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = useCase(currentVersionCode = 3)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result is UpdateCheckResult.NotConfigured)
|
||||||
|
coVerify(exactly = 0) { updateRepository.checkForUpdate(any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_invoke_serverUrlIsBlank_returnsNotConfigured() = runTest {
|
||||||
|
// Given
|
||||||
|
setupServerUrl(" ")
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = useCase(currentVersionCode = 3)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result is UpdateCheckResult.NotConfigured)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_invoke_networkError_returnsError() = runTest {
|
||||||
|
// Given
|
||||||
|
setupServerUrl("https://example.com")
|
||||||
|
val networkError = ConnectException("Connection refused")
|
||||||
|
coEvery { updateRepository.checkForUpdate("https://example.com") } returns Result.failure(networkError)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = useCase(currentVersionCode = 3)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertTrue(result is UpdateCheckResult.Error)
|
||||||
|
assertEquals(networkError, (result as UpdateCheckResult.Error).cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue