diff --git a/feature/data/ipconfig/src/main/kotlin/com/soberg/netinfo/data/ipconfig/IpConfigKtorClient.kt b/feature/data/ipconfig/src/main/kotlin/com/soberg/netinfo/data/ipconfig/IpConfigKtorClient.kt index f88ec64..8c8c9e7 100644 --- a/feature/data/ipconfig/src/main/kotlin/com/soberg/netinfo/data/ipconfig/IpConfigKtorClient.kt +++ b/feature/data/ipconfig/src/main/kotlin/com/soberg/netinfo/data/ipconfig/IpConfigKtorClient.kt @@ -20,13 +20,15 @@ object IpConfigKtorClient { fun create( engineFactory: HttpClientEngineFactory = OkHttp, - ) = HttpClient(engineFactory) { applyForIpConfig() } + includeTimeout: Boolean = true, + ) = HttpClient(engineFactory) { applyForIpConfig(includeTimeout = includeTimeout) } fun create( engine: HttpClientEngine, - ) = HttpClient(engine) { applyForIpConfig() } + includeTimeout: Boolean = true, + ) = HttpClient(engine) { applyForIpConfig(includeTimeout = includeTimeout) } - private fun HttpClientConfig<*>.applyForIpConfig() { + private fun HttpClientConfig<*>.applyForIpConfig(includeTimeout: Boolean) { install(ContentNegotiation) { json( Json { @@ -35,10 +37,12 @@ object IpConfigKtorClient { } ) } - install(HttpTimeout) { - socketTimeoutMillis = Timeout.inWholeMilliseconds - requestTimeoutMillis = Timeout.inWholeMilliseconds - connectTimeoutMillis = Timeout.inWholeMilliseconds + if (includeTimeout) { + install(HttpTimeout) { + socketTimeoutMillis = Timeout.inWholeMilliseconds + requestTimeoutMillis = Timeout.inWholeMilliseconds + connectTimeoutMillis = Timeout.inWholeMilliseconds + } } defaultRequest { contentType(ContentType.Application.Json) diff --git a/feature/data/ipconfig/src/test/kotlin/com/soberg/netinfo/data/ipconfig/IpConfigWanInfoRepositoryTest.kt b/feature/data/ipconfig/src/test/kotlin/com/soberg/netinfo/data/ipconfig/IpConfigWanInfoRepositoryTest.kt index 0e47dd1..77c38c3 100644 --- a/feature/data/ipconfig/src/test/kotlin/com/soberg/netinfo/data/ipconfig/IpConfigWanInfoRepositoryTest.kt +++ b/feature/data/ipconfig/src/test/kotlin/com/soberg/netinfo/data/ipconfig/IpConfigWanInfoRepositoryTest.kt @@ -18,7 +18,6 @@ import io.ktor.client.request.get import io.ktor.http.HttpHeaders import io.ktor.http.HttpStatusCode import io.ktor.http.headersOf -import io.ktor.utils.io.ByteReadChannel import io.mockk.coEvery import io.mockk.coVerify import io.mockk.mockk @@ -105,14 +104,10 @@ internal class IpConfigWanInfoRepositoryTest { country = Country( name = "United States", iso = "US", - ), - region = Region( + ), region = Region( name = "New Jersey", code = "NJ", - ), - zipCode = ZipCode("08601"), - cityName = "Trenton", - location = Location( + ), zipCode = ZipCode("08601"), cityName = "Trenton", location = Location( latitude = 40.2206, longitude = -74.7597, ) @@ -146,56 +141,53 @@ internal class IpConfigWanInfoRepositoryTest { } @Test - fun `retry when UnknownHostException received`() = - runTest(ioDispatcher) { - val query: HttpQuery = mockk { - coEvery { this@mockk.invoke(IpConfigUrl.Main) } throws UnknownHostException("Test") - } - val repo = IpConfigWanInfoRepository(ioDispatcher, query) - - val result = repo.loadWanInfo() - assertThat(result).isEqualTo(WanInfoRepository.Result.Error) - coVerify(exactly = 2) { query.invoke(IpConfigUrl.Main) } + fun `retry when UnknownHostException received`() = runTest(ioDispatcher) { + val query: HttpQuery = mockk { + coEvery { this@mockk.invoke(IpConfigUrl.Main) } throws UnknownHostException("Test") } + val repo = IpConfigWanInfoRepository(ioDispatcher, query) - @Test - fun `retry when ConnectException received`() = - runTest(ioDispatcher) { - val query: HttpQuery = mockk { - coEvery { this@mockk.invoke(IpConfigUrl.Main) } throws ConnectException("Test") - } - val repo = IpConfigWanInfoRepository(ioDispatcher, query) + val result = repo.loadWanInfo() + assertThat(result).isEqualTo(WanInfoRepository.Result.Error) + coVerify(exactly = 2) { query.invoke(IpConfigUrl.Main) } + } - val result = repo.loadWanInfo() - assertThat(result).isEqualTo(WanInfoRepository.Result.Error) - coVerify(exactly = 2) { query.invoke(IpConfigUrl.Main) } + @Test + fun `retry when ConnectException received`() = runTest(ioDispatcher) { + val query: HttpQuery = mockk { + coEvery { this@mockk.invoke(IpConfigUrl.Main) } throws ConnectException("Test") } + val repo = IpConfigWanInfoRepository(ioDispatcher, query) - @Test - fun `not retry when generic exception received`() = - runTest(ioDispatcher) { - val query: HttpQuery = mockk { - coEvery { this@mockk.invoke(IpConfigUrl.Main) } throws IllegalStateException("Test") - } - val repo = IpConfigWanInfoRepository(ioDispatcher, query) + val result = repo.loadWanInfo() + assertThat(result).isEqualTo(WanInfoRepository.Result.Error) + coVerify(exactly = 2) { query.invoke(IpConfigUrl.Main) } + } - val result = repo.loadWanInfo() - assertThat(result).isEqualTo(WanInfoRepository.Result.Error) - coVerify(exactly = 1) { query.invoke(IpConfigUrl.Main) } + @Test + fun `not retry when generic exception received`() = runTest(ioDispatcher) { + val query: HttpQuery = mockk { + coEvery { this@mockk.invoke(IpConfigUrl.Main) } throws IllegalStateException("Test") } + val repo = IpConfigWanInfoRepository(ioDispatcher, query) + + val result = repo.loadWanInfo() + assertThat(result).isEqualTo(WanInfoRepository.Result.Error) + coVerify(exactly = 1) { query.invoke(IpConfigUrl.Main) } + } private fun create( bodyContent: String, statusCode: HttpStatusCode, ): IpConfigWanInfoRepository { - val mockEngine = MockEngine { + val mockEngine = MockEngine { _ -> respond( - content = ByteReadChannel(bodyContent), + content = bodyContent, status = statusCode, headers = headersOf(HttpHeaders.ContentType, "application/json") ) } - val client = IpConfigKtorClient.create(mockEngine) + val client = IpConfigKtorClient.create(mockEngine, includeTimeout = false) val query = HttpQuery { url -> client.get(url) } return IpConfigWanInfoRepository(ioDispatcher, query) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f5b7a09..02a73b5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,29 +1,29 @@ [versions] androidGradlePlugin = "9.2.1" -androidxActivity = "1.11.0" -androidxLifecycle = "2.9.4" +androidxActivity = "1.13.0" +androidxLifecycle = "2.10.0" androidxCoreTest = "1.7.0" assertk = "0.28.1" composeBom = "2026.05.01" -composeNavigation = "2.9.5" +composeNavigation = "2.9.8" dagger = "2.59.2" kotlinMetadata = "2.4.0" -dependencyAnalysis = "3.2.0" +dependencyAnalysis = "3.15.0" googleAccompanist = "0.36.0" -googleGuava = "33.5.0-jre" -googleMaterial = "1.13.0" +googleGuava = "33.6.0-jre" +googleMaterial = "1.14.0" javapoet = "1.13.0" junit4 = "4.13.2" -junitJupiterBom = "6.0.0" +junitJupiterBom = "6.1.0" kotlin = "2.4.0" kotlinCoroutines = "1.11.0" kotlinSerialization = "1.11.0" # KSP version must align with Kotlin version: https://github.com/google/ksp/releases ksp = "2.3.9" -ktor = "3.3.1" -lottie = "6.6.10" -mockk = "1.14.6" -robolectric = "4.16" +ktor = "3.5.0" +lottie = "6.7.1" +mockk = "1.14.11" +robolectric = "4.16.1" turbine = "1.2.1" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8bdaf60..b1b8ef5 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c61a118..df6a6ad 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index adff685..b9bb139 100755 --- a/gradlew +++ b/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/gradlew.bat b/gradlew.bat index e509b2d..aa5f10b 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -23,8 +23,8 @@ @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,7 +65,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line @@ -73,21 +73,10 @@ goto fail @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL%