From 2b7facb7f4e2eb5c389d84b44d16fa412a0228c7 Mon Sep 17 00:00:00 2001 From: M57 <41645758+Hy4ri@users.noreply.github.com> Date: Sat, 20 Jun 2026 23:52:23 +0300 Subject: [PATCH] Add NetworkResult Test for SafeApiCall and Mapping (#185) * Add tests for NetworkResult and safeApiCall * Fix ktlint errors formatting --- .../data/remote/NetworkResultTest.kt | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 app/src/test/java/com/m57/hermescontrol/data/remote/NetworkResultTest.kt diff --git a/app/src/test/java/com/m57/hermescontrol/data/remote/NetworkResultTest.kt b/app/src/test/java/com/m57/hermescontrol/data/remote/NetworkResultTest.kt new file mode 100644 index 00000000..16eebdee --- /dev/null +++ b/app/src/test/java/com/m57/hermescontrol/data/remote/NetworkResultTest.kt @@ -0,0 +1,175 @@ +package com.m57.hermescontrol.data.remote + +import kotlinx.coroutines.runBlocking +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.ResponseBody.Companion.toResponseBody +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import retrofit2.Response +import java.io.IOException + +class NetworkResultTest { + @Test + fun testMapHttpError_knownCodes() { + val codesAndExpectedMessages = + mapOf( + 400 to "Bad Request (HTTP 400): The server could not understand the request.", + 401 to "Unauthorized (HTTP 401): Authentication token is invalid or expired.", + 403 to "Forbidden (HTTP 403): You do not have permission to access this resource.", + 404 to "Not Found (HTTP 404): The requested resource could not be found.", + 405 to "Method Not Allowed (HTTP 405).", + 408 to "Request Timeout (HTTP 408): The server timed out waiting for the request.", + 409 to "Conflict (HTTP 409): The request conflicts with current server state.", + 429 to "Too Many Requests (HTTP 429): Rate limit exceeded.", + 500 to "Server Error (HTTP 500): The server encountered an error.", + 599 to "Server Error (HTTP 599): The server encountered an error.", + ) + + for ((code, expectedMessage) in codesAndExpectedMessages) { + val error = mapHttpError(code) + assertTrue(error is NetworkError.Http) + error as NetworkError.Http + assertEquals(code, error.code) + assertEquals(expectedMessage, error.message) + } + } + + @Test + fun testMapHttpError_unknownCode() { + val error = mapHttpError(418) + assertTrue(error is NetworkError.Http) + error as NetworkError.Http + assertEquals(418, error.code) + assertEquals("HTTP Error 418: Unexpected server response.", error.message) + } + + @Test + fun testSafeApiCall_successWithBody() = + runBlocking { + val result = + safeApiCall { + Response.success("Success") + } + assertEquals(NetworkResult.Success::class, result::class) + assertEquals("Success", (result as NetworkResult.Success).data) + } + + @Test + fun testSafeApiCall_successWithNullBody() = + runBlocking { + val result = + safeApiCall { + Response.success(null) + } + if (result is NetworkResult.Failure) { + println("FAILURE: " + result.error) + } + assertEquals(NetworkResult.Success::class, result::class) + assertEquals(null, (result as NetworkResult.Success).data) + } + + @Test + fun testSafeApiCall_httpFailure_nonRetryable() = + runBlocking { + var callCount = 0 + val result = + safeApiCall { + callCount++ + Response.error(404, "Not Found".toResponseBody("text/plain".toMediaTypeOrNull())) + } + assertTrue(result is NetworkResult.Failure) + val error = (result as NetworkResult.Failure).error + assertTrue(error is NetworkError.Http) + assertEquals(404, (error as NetworkError.Http).code) + assertEquals(1, callCount) // Should not retry for 4xx errors + } + + @Test + fun testSafeApiCall_httpFailure_retryable_eventuallySucceeds() = + runBlocking { + var callCount = 0 + val result = + safeApiCall { + callCount++ + if (callCount == 1) { + Response.error(500, "Server Error".toResponseBody("text/plain".toMediaTypeOrNull())) + } else { + Response.success("Success on retry") + } + } + assertEquals(NetworkResult.Success::class, result::class) + assertEquals("Success on retry", (result as NetworkResult.Success).data) + assertEquals(2, callCount) + } + + @Test + fun testSafeApiCall_httpFailure_retryable_exceedsMaxRetries() = + runBlocking { + var callCount = 0 + val result = + safeApiCall(retries = 2) { + callCount++ + Response.error(503, "Service Unavailable".toResponseBody("text/plain".toMediaTypeOrNull())) + } + assertTrue(result is NetworkResult.Failure) + val error = (result as NetworkResult.Failure).error + assertTrue(error is NetworkError.Http) + assertEquals(503, (error as NetworkError.Http).code) + assertEquals(3, callCount) // 1 initial + 2 retries + } + + @Test + fun testSafeApiCall_ioException_eventuallySucceeds() = + runBlocking { + var callCount = 0 + val result = + safeApiCall { + callCount++ + if (callCount == 1) { + throw IOException("Network error") + } else { + Response.success("Success on retry") + } + } + assertEquals(NetworkResult.Success::class, result::class) + assertEquals("Success on retry", (result as NetworkResult.Success).data) + assertEquals(2, callCount) + } + + @Test + fun testSafeApiCall_ioException_exceedsMaxRetries() = + runBlocking { + var callCount = 0 + val result = + safeApiCall(retries = 2) { + callCount++ + throw IOException("Network timeout") + } + assertTrue(result is NetworkResult.Failure) + val error = (result as NetworkResult.Failure).error + assertTrue(error is NetworkError.Connection) + val connectionError = error as NetworkError.Connection + assertTrue(connectionError.message.contains("Network connection failure")) + assertTrue(connectionError.message.contains("Network timeout")) + assertEquals(3, callCount) // 1 initial + 2 retries + } + + @Test + fun testSafeApiCall_otherException() = + runBlocking { + var callCount = 0 + val result = + safeApiCall { + callCount++ + throw RuntimeException("Unexpected error") + } + assertTrue(result is NetworkResult.Failure) + val error = (result as NetworkResult.Failure).error + assertTrue(error is NetworkError.Unknown) + val unknownError = error as NetworkError.Unknown + assertTrue(unknownError.message.contains("An unexpected error occurred")) + assertTrue(unknownError.message.contains("Unexpected error")) + assertEquals(1, callCount) // Should not retry for unknown exceptions + } +}