From 472b7591f5103ac4e51900212392c740c3ad8e1c Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Sat, 18 Sep 2021 13:55:58 +0300 Subject: [PATCH 1/3] Update dependencies and make adjustments based on version migrations --- app/build.gradle | 11 +-- .../AndroidTestServerTypeConfiguration.kt | 2 +- .../FavouriteContentLocalStorageImplTest.kt | 15 ++- .../showcase/ui/auth/CodeKataAuthViewModel.kt | 2 + .../ui/splash/CodeKataSplashViewModelTest.kt | 2 + app/src/test/resources/robolectric.properties | 1 + build.gradle | 6 +- core/build.gradle | 9 +- .../core/content/ContentRepository.kt | 3 + .../test/showcase/core/di/createCoreModule.kt | 2 +- .../content/CodeKataContentRepositoryTest.kt | 6 +- .../core/content/GetAllContentUseCaseTest.kt | 93 ++++++++++--------- .../login/CodeKataSecondLoginUseCaseTest.kt | 6 +- ...deKataFirstSessionExpirationAdapterTest.kt | 2 + .../org.mockito.plugins.MockMaker | 2 +- gradle.properties | 1 + gradlescripts/versions.gradle | 24 ++--- mockserver/build.gradle | 2 - .../mockserver/MockServerScenarioSetup.kt | 2 +- model/build.gradle | 6 +- .../test/showcase/model/content/ContentId.kt | 3 +- .../test/showcase/model/content/ImageUrl.kt | 3 +- .../test/showcase/model/network/BaseUrl.kt | 3 +- network/build.gradle | 7 +- .../network/di/createNetworkmodules.kt | 2 +- .../auth/CodeKataLoginRemoteSourceTest.kt | 1 + .../content/CodeKataSessionExpirationTest.kt | 1 + 27 files changed, 120 insertions(+), 97 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2a7b3cf..d63bebe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -83,20 +83,17 @@ afterEvaluate { } dependencies { - - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "androidx.core:core-ktx:$androidx_core_version" implementation "androidx.appcompat:appcompat:$androidx_appcompat_version" implementation "com.google.android.material:material:$androidx_material_version" implementation "androidx.constraintlayout:constraintlayout:$androidx_constraintlayout_version" implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$androidx_livedata_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$androidx_livedata_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$androidx_livedata_version" implementation "androidx.swiperefreshlayout:swiperefreshlayout:$androidx_swiperefreshlayout_version" // Koin - implementation "org.koin:koin-androidx-scope:$koin_version" - implementation "org.koin:koin-androidx-viewmodel:$koin_version" - implementation "org.koin:koin-androidx-fragment:$koin_version" + implementation "io.insert-koin:koin-android:$koin_version" implementation "androidx.room:room-runtime:$androidx_room_version" kapt "androidx.room:room-compiler:$androidx_room_version" @@ -113,7 +110,7 @@ dependencies { testImplementation "org.mockito.kotlin:mockito-kotlin:$testing_kotlin_mockito_version" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" testImplementation "com.jraska.livedata:testing-ktx:$testing_livedata_version" - testImplementation "org.koin:koin-test:$koin_version" + testImplementation "io.insert-koin:koin-test-junit5:$koin_version" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version" // robolectric specific @@ -130,7 +127,7 @@ dependencies { testRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version" androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" - androidTestImplementation "org.koin:koin-test:$koin_version" + androidTestImplementation "io.insert-koin:koin-test-junit4:$koin_version" androidTestImplementation "junit:junit:$testing_junit4_version" androidTestImplementation "androidx.test:core:$testing_androidx_code_version" androidTestImplementation "androidx.test:runner:$testing_androidx_code_version" diff --git a/app/src/androidTest/java/org/fnives/test/showcase/testutils/configuration/AndroidTestServerTypeConfiguration.kt b/app/src/androidTest/java/org/fnives/test/showcase/testutils/configuration/AndroidTestServerTypeConfiguration.kt index 46a59a3..319668e 100644 --- a/app/src/androidTest/java/org/fnives/test/showcase/testutils/configuration/AndroidTestServerTypeConfiguration.kt +++ b/app/src/androidTest/java/org/fnives/test/showcase/testutils/configuration/AndroidTestServerTypeConfiguration.kt @@ -22,7 +22,7 @@ object AndroidTestServerTypeConfiguration : ServerTypeConfiguration, KoinTest { .build() loadKoinModules( module { - single(qualifier = sessionless, override = true) { okHttpClientWithCertificate } + single(qualifier = sessionless) { okHttpClientWithCertificate } } ) } diff --git a/app/src/robolectricTest/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt b/app/src/robolectricTest/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt index da79993..06d314b 100644 --- a/app/src/robolectricTest/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt +++ b/app/src/robolectricTest/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt @@ -1,12 +1,16 @@ package org.fnives.test.showcase.favourite import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.runBlockingTest import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage import org.fnives.test.showcase.model.content.ContentId import org.fnives.test.showcase.storage.database.DatabaseInitialization @@ -20,6 +24,7 @@ import org.koin.test.KoinTest import org.koin.test.inject @Suppress("TestFunctionName") +@OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) internal class FavouriteContentLocalStorageImplTest : KoinTest { @@ -39,12 +44,12 @@ internal class FavouriteContentLocalStorageImplTest : KoinTest { @Test fun GIVEN_content_id_WHEN_added_to_Favourite_THEN_it_can_be_read_out() = runBlocking { - val expected = listOf(ContentId("a")) + val expected = listOf(ContentId("a")) - sut.markAsFavourite(ContentId("a")) - val actual = sut.observeFavourites().first() + sut.markAsFavourite(ContentId("a")) + val actual = sut.observeFavourites().first() - Assert.assertEquals(expected, actual) + Assert.assertEquals(expected, actual) } @Test @@ -63,6 +68,7 @@ internal class FavouriteContentLocalStorageImplTest : KoinTest { runBlocking { val expected = listOf(listOf(), listOf(ContentId("a"))) + val testDispatcher = TestCoroutineDispatcher() val actual = async(testDispatcher) { sut.observeFavourites().take(2).toList() } @@ -79,6 +85,7 @@ internal class FavouriteContentLocalStorageImplTest : KoinTest { val expected = listOf(listOf(ContentId("a")), listOf()) sut.markAsFavourite(ContentId("a")) + val testDispatcher = TestCoroutineDispatcher() val actual = async(testDispatcher) { sut.observeFavourites().take(2).toList() } diff --git a/app/src/test/java/org/fnives/test/showcase/ui/auth/CodeKataAuthViewModel.kt b/app/src/test/java/org/fnives/test/showcase/ui/auth/CodeKataAuthViewModel.kt index cff5a46..917f67e 100644 --- a/app/src/test/java/org/fnives/test/showcase/ui/auth/CodeKataAuthViewModel.kt +++ b/app/src/test/java/org/fnives/test/showcase/ui/auth/CodeKataAuthViewModel.kt @@ -4,11 +4,13 @@ import org.fnives.test.showcase.core.login.LoginUseCase import org.fnives.test.showcase.testutils.InstantExecutorExtension import org.fnives.test.showcase.testutils.TestMainDispatcher import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.mockito.kotlin.mock +@Disabled("CodeKata") @ExtendWith(InstantExecutorExtension::class, TestMainDispatcher::class) class CodeKataAuthViewModel { diff --git a/app/src/test/java/org/fnives/test/showcase/ui/splash/CodeKataSplashViewModelTest.kt b/app/src/test/java/org/fnives/test/showcase/ui/splash/CodeKataSplashViewModelTest.kt index 26d39b2..4e6738d 100644 --- a/app/src/test/java/org/fnives/test/showcase/ui/splash/CodeKataSplashViewModelTest.kt +++ b/app/src/test/java/org/fnives/test/showcase/ui/splash/CodeKataSplashViewModelTest.kt @@ -5,6 +5,7 @@ import org.fnives.test.showcase.testutils.InstantExecutorExtension import org.fnives.test.showcase.testutils.TestMainDispatcher import org.fnives.test.showcase.ui.shared.Event import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -12,6 +13,7 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.whenever +@Disabled("CodeKata") internal class CodeKataSplashViewModelTest { @BeforeEach diff --git a/app/src/test/resources/robolectric.properties b/app/src/test/resources/robolectric.properties index 0b813b6..bcd587b 100644 --- a/app/src/test/resources/robolectric.properties +++ b/app/src/test/resources/robolectric.properties @@ -1,2 +1,3 @@ sdk=28 shadows=org.fnives.test.showcase.testutils.shadow.ShadowSnackbar +instrumentedPackages=androidx.loader.content diff --git a/build.gradle b/build.gradle index f688044..a53abdc 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,11 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = "1.4.32" + ext.kotlin_version = "1.5.30" ext.detekt_version = "1.16.0" repositories { + mavenCentral() google() maven { url "https://plugins.gradle.org/m2/" } - jcenter() } dependencies { classpath "com.android.tools.build:gradle:4.1.3" @@ -47,8 +47,8 @@ detekt { allprojects { repositories { + mavenCentral() google() - jcenter() } } diff --git a/core/build.gradle b/core/build.gradle index 1e5acf0..5d442cc 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -17,14 +17,19 @@ test { } } +compileKotlin { + kotlinOptions { + freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" + } +} + dependencies { - api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" api project(":model") implementation project(":network") - testImplementation "org.koin:koin-test:$koin_version" + testImplementation "io.insert-koin:koin-test-junit5:$koin_version" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" testImplementation "org.mockito.kotlin:mockito-kotlin:$testing_kotlin_mockito_version" testImplementation "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version" diff --git a/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt b/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt index 519110d..b243db5 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/content/ContentRepository.kt @@ -1,5 +1,6 @@ package org.fnives.test.showcase.core.content +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged @@ -24,6 +25,8 @@ internal class ContentRepository(private val contentRemoteSource: ContentRemoteS } emit(response) } + + @OptIn(ExperimentalCoroutinesApi::class) val contents: Flow>> = mutableContentFlow.flatMapLatest { if (it.item != null) flowOf(Resource.Success(it.item)) else requestFlow } diff --git a/core/src/main/java/org/fnives/test/showcase/core/di/createCoreModule.kt b/core/src/main/java/org/fnives/test/showcase/core/di/createCoreModule.kt index a2b18d1..d3b6bc5 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/di/createCoreModule.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/di/createCoreModule.kt @@ -37,7 +37,7 @@ fun createCoreModule( .plus(repositoryModule()) fun repositoryModule() = module { - single(override = true) { ContentRepository(get()) } + single { ContentRepository(get()) } } fun useCaseModule() = module { diff --git a/core/src/test/java/org/fnives/test/showcase/core/content/CodeKataContentRepositoryTest.kt b/core/src/test/java/org/fnives/test/showcase/core/content/CodeKataContentRepositoryTest.kt index 49165de..c8f74fe 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/content/CodeKataContentRepositoryTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/content/CodeKataContentRepositoryTest.kt @@ -12,10 +12,7 @@ import org.fnives.test.showcase.model.content.ContentId import org.fnives.test.showcase.model.content.ImageUrl import org.fnives.test.showcase.model.shared.Resource import org.fnives.test.showcase.network.content.ContentRemoteSource -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test +import org.junit.jupiter.api.* import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.doSuspendableAnswer @@ -26,6 +23,7 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever +@Disabled("CodeKata") class CodeKataContentRepositoryTest { @BeforeEach diff --git a/core/src/test/java/org/fnives/test/showcase/core/content/GetAllContentUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/content/GetAllContentUseCaseTest.kt index 1fd4406..5e36cd8 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/content/GetAllContentUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/content/GetAllContentUseCaseTest.kt @@ -1,5 +1,6 @@ package org.fnives.test.showcase.core.content +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.take @@ -20,6 +21,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.whenever @Suppress("TestFunctionName") +@OptIn(ExperimentalCoroutinesApi::class) internal class GetAllContentUseCaseTest { private lateinit var sut: GetAllContentUseCase @@ -36,71 +38,78 @@ internal class GetAllContentUseCaseTest { mockContentRepository = mock() favouriteContentIdFlow = MutableStateFlow(emptyList()) contentFlow = MutableStateFlow(Resource.Loading()) - whenever(mockFavouriteContentLocalStorage.observeFavourites()).doReturn(favouriteContentIdFlow) + whenever(mockFavouriteContentLocalStorage.observeFavourites()).doReturn( + favouriteContentIdFlow + ) whenever(mockContentRepository.contents).doReturn(contentFlow) sut = GetAllContentUseCase(mockContentRepository, mockFavouriteContentLocalStorage) } @Test - fun GIVEN_loading_AND_empty_favourite_WHEN_observed_THEN_loading_is_shown() = runBlockingTest(testDispatcher) { - favouriteContentIdFlow.value = emptyList() - contentFlow.value = Resource.Loading() - val expected = Resource.Loading>() + fun GIVEN_loading_AND_empty_favourite_WHEN_observed_THEN_loading_is_shown() = + runBlockingTest(testDispatcher) { + favouriteContentIdFlow.value = emptyList() + contentFlow.value = Resource.Loading() + val expected = Resource.Loading>() - val actual = sut.get().take(1).toList() + val actual = sut.get().take(1).toList() - Assertions.assertEquals(listOf(expected), actual) - } + Assertions.assertEquals(listOf(expected), actual) + } @Test - fun GIVEN_loading_AND_listOfFavourite_WHEN_observed_THEN_loading_is_shown() = runBlockingTest(testDispatcher) { - favouriteContentIdFlow.value = listOf(ContentId("a")) - contentFlow.value = Resource.Loading() - val expected = Resource.Loading>() + fun GIVEN_loading_AND_listOfFavourite_WHEN_observed_THEN_loading_is_shown() = + runBlockingTest(testDispatcher) { + favouriteContentIdFlow.value = listOf(ContentId("a")) + contentFlow.value = Resource.Loading() + val expected = Resource.Loading>() - val actual = sut.get().take(1).toList() + val actual = sut.get().take(1).toList() - Assertions.assertEquals(listOf(expected), actual) - } + Assertions.assertEquals(listOf(expected), actual) + } @Test - fun GIVEN_error_AND_empty_favourite_WHEN_observed_THEN_error_is_shown() = runBlockingTest(testDispatcher) { - favouriteContentIdFlow.value = emptyList() - val exception = Throwable() - contentFlow.value = Resource.Error(exception) - val expected = Resource.Error>(exception) + fun GIVEN_error_AND_empty_favourite_WHEN_observed_THEN_error_is_shown() = + runBlockingTest(testDispatcher) { + favouriteContentIdFlow.value = emptyList() + val exception = Throwable() + contentFlow.value = Resource.Error(exception) + val expected = Resource.Error>(exception) - val actual = sut.get().take(1).toList() + val actual = sut.get().take(1).toList() - Assertions.assertEquals(listOf(expected), actual) - } + Assertions.assertEquals(listOf(expected), actual) + } @Test - fun GIVEN_error_AND_listOfFavourite_WHEN_observed_THEN_error_is_shown() = runBlockingTest(testDispatcher) { - favouriteContentIdFlow.value = listOf(ContentId("b")) - val exception = Throwable() - contentFlow.value = Resource.Error(exception) - val expected = Resource.Error>(exception) + fun GIVEN_error_AND_listOfFavourite_WHEN_observed_THEN_error_is_shown() = + runBlockingTest(testDispatcher) { + favouriteContentIdFlow.value = listOf(ContentId("b")) + val exception = Throwable() + contentFlow.value = Resource.Error(exception) + val expected = Resource.Error>(exception) - val actual = sut.get().take(1).toList() + val actual = sut.get().take(1).toList() - Assertions.assertEquals(listOf(expected), actual) - } + Assertions.assertEquals(listOf(expected), actual) + } @Test - fun GIVEN_listOfContent_AND_empty_favourite_WHEN_observed_THEN_favourites_are_returned() = runBlockingTest(testDispatcher) { - favouriteContentIdFlow.value = emptyList() - val content = Content(ContentId("a"), "b", "c", ImageUrl("d")) - contentFlow.value = Resource.Success(listOf(content)) - val items = listOf( - FavouriteContent(content, false) - ) - val expected = Resource.Success(items) + fun GIVEN_listOfContent_AND_empty_favourite_WHEN_observed_THEN_favourites_are_returned() = + runBlockingTest(testDispatcher) { + favouriteContentIdFlow.value = emptyList() + val content = Content(ContentId("a"), "b", "c", ImageUrl("d")) + contentFlow.value = Resource.Success(listOf(content)) + val items = listOf( + FavouriteContent(content, false) + ) + val expected = Resource.Success(items) - val actual = sut.get().take(1).toList() + val actual = sut.get().take(1).toList() - Assertions.assertEquals(listOf(expected), actual) - } + Assertions.assertEquals(listOf(expected), actual) + } @Test fun GIVEN_listOfContent_AND_other_favourite_id_WHEN_observed_THEN_favourites_are_returned() = diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/CodeKataSecondLoginUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/login/CodeKataSecondLoginUseCaseTest.kt index 306443d..3bef542 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/login/CodeKataSecondLoginUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/login/CodeKataSecondLoginUseCaseTest.kt @@ -9,12 +9,10 @@ import org.fnives.test.showcase.model.session.Session import org.fnives.test.showcase.model.shared.Answer import org.fnives.test.showcase.network.auth.LoginRemoteSource import org.fnives.test.showcase.network.auth.model.LoginStatusResponses -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test +import org.junit.jupiter.api.* import org.mockito.kotlin.* +@Disabled("CodeKata") class CodeKataSecondLoginUseCaseTest { @BeforeEach diff --git a/core/src/test/java/org/fnives/test/showcase/core/session/CodeKataFirstSessionExpirationAdapterTest.kt b/core/src/test/java/org/fnives/test/showcase/core/session/CodeKataFirstSessionExpirationAdapterTest.kt index 5b50495..686fa7f 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/session/CodeKataFirstSessionExpirationAdapterTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/session/CodeKataFirstSessionExpirationAdapterTest.kt @@ -1,10 +1,12 @@ package org.fnives.test.showcase.core.session import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.mockito.kotlin.* +@Disabled("CodeKata") class CodeKataFirstSessionExpirationAdapterTest { } \ No newline at end of file diff --git a/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker index 092e268..ca6ee9c 100644 --- a/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker +++ b/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -1 +1 @@ -#mock-maker-inline \ No newline at end of file +mock-maker-inline \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 98bed16..f9842bc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,5 +17,6 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true +android.jetifier.blacklist=bcprov-jdk15on # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official \ No newline at end of file diff --git a/gradlescripts/versions.gradle b/gradlescripts/versions.gradle index 576eb20..4f8ed25 100644 --- a/gradlescripts/versions.gradle +++ b/gradlescripts/versions.gradle @@ -1,28 +1,28 @@ project.ext { - androidx_core_version = "1.3.2" - androidx_appcompat_version = "1.2.0" - androidx_material_version = "1.3.0" - androidx_constraintlayout_version = "2.0.4" + androidx_core_version = "1.6.0" + androidx_appcompat_version = "1.3.1" + androidx_material_version = "1.4.0" + androidx_constraintlayout_version = "2.1.0" androidx_livedata_version = "2.3.1" androidx_swiperefreshlayout_version = "1.1.0" - androidx_room_version = "2.2.6" + androidx_room_version = "2.3.0" coroutines_version = "1.4.3" - koin_version = "2.2.2" + koin_version = "3.1.2" coil_version = "1.1.1" retrofit_version = "2.9.0" okhttp_version = "4.9.1" - moshi_version = "1.11.0" + moshi_version = "1.12.0" - testing_androidx_code_version = "1.3.0" - testing_androidx_junit_version = "1.1.2" + testing_androidx_code_version = "1.4.0" + testing_androidx_junit_version = "1.1.3" testing_androidx_arch_core_version = "2.1.0" - testing_livedata_version = "1.1.2" + testing_livedata_version = "1.2.0" testing_kotlin_mockito_version = "3.1.0" testing_junit5_version = "5.7.0" testing_json_assert_version = "1.5.0" testing_junit4_version = "4.13.2" - testing_robolectric_version = "4.5.1" - testing_espresso_version = "3.3.0" + testing_robolectric_version = "4.6.1" + testing_espresso_version = "3.4.0" testing_okhttp3_idling_resource_version = "1.0.0" } \ No newline at end of file diff --git a/mockserver/build.gradle b/mockserver/build.gradle index acda750..4b481ef 100644 --- a/mockserver/build.gradle +++ b/mockserver/build.gradle @@ -9,8 +9,6 @@ java { } dependencies { - api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - api project(":model") api "com.squareup.okhttp3:mockwebserver:$okhttp_version" diff --git a/mockserver/src/main/java/org/fnives/test/showcase/network/mockserver/MockServerScenarioSetup.kt b/mockserver/src/main/java/org/fnives/test/showcase/network/mockserver/MockServerScenarioSetup.kt index 6946a59..b1da9c2 100644 --- a/mockserver/src/main/java/org/fnives/test/showcase/network/mockserver/MockServerScenarioSetup.kt +++ b/mockserver/src/main/java/org/fnives/test/showcase/network/mockserver/MockServerScenarioSetup.kt @@ -70,7 +70,7 @@ class MockServerScenarioSetup internal constructor( companion object { const val PORT: Int = 7335 - val HTTP_BASE_URL get() = "http://${InetAddress.getLocalHost().hostName}" + val HTTP_BASE_URL get() = "http://${InetAddress.getLocalHost().canonicalHostName}" val HTTPS_BASE_URL get() = "https://localhost" private fun MockWebServer.useHttps(): HandshakeCertificates { diff --git a/model/build.gradle b/model/build.gradle index 505234c..e30cb2f 100644 --- a/model/build.gradle +++ b/model/build.gradle @@ -10,10 +10,6 @@ java { compileKotlin { kotlinOptions { - freeCompilerArgs = ["-Xinline-classes"] + freeCompilerArgs += "-Xinline-classes" } } - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" -} \ No newline at end of file diff --git a/model/src/main/java/org/fnives/test/showcase/model/content/ContentId.kt b/model/src/main/java/org/fnives/test/showcase/model/content/ContentId.kt index c0b2c83..cc76cea 100644 --- a/model/src/main/java/org/fnives/test/showcase/model/content/ContentId.kt +++ b/model/src/main/java/org/fnives/test/showcase/model/content/ContentId.kt @@ -1,3 +1,4 @@ package org.fnives.test.showcase.model.content -inline class ContentId(val id: String) +@JvmInline +value class ContentId(val id: String) diff --git a/model/src/main/java/org/fnives/test/showcase/model/content/ImageUrl.kt b/model/src/main/java/org/fnives/test/showcase/model/content/ImageUrl.kt index b304fa8..a58c38b 100644 --- a/model/src/main/java/org/fnives/test/showcase/model/content/ImageUrl.kt +++ b/model/src/main/java/org/fnives/test/showcase/model/content/ImageUrl.kt @@ -1,3 +1,4 @@ package org.fnives.test.showcase.model.content -inline class ImageUrl(val url: String) +@JvmInline +value class ImageUrl(val url: String) diff --git a/model/src/main/java/org/fnives/test/showcase/model/network/BaseUrl.kt b/model/src/main/java/org/fnives/test/showcase/model/network/BaseUrl.kt index 1b8a6e4..53fb2ba 100644 --- a/model/src/main/java/org/fnives/test/showcase/model/network/BaseUrl.kt +++ b/model/src/main/java/org/fnives/test/showcase/model/network/BaseUrl.kt @@ -1,3 +1,4 @@ package org.fnives.test.showcase.model.network -inline class BaseUrl(val baseUrl: String) +@JvmInline +value class BaseUrl(val baseUrl: String) diff --git a/network/build.gradle b/network/build.gradle index 9a11548..d49020e 100644 --- a/network/build.gradle +++ b/network/build.gradle @@ -19,7 +19,6 @@ test { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "com.squareup.retrofit2:retrofit:$retrofit_version" @@ -27,15 +26,15 @@ dependencies { implementation "com.squareup.moshi:moshi:$moshi_version" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version" - api "org.koin:koin-core:$koin_version" + api "io.insert-koin:koin-core:$koin_version" api project(":model") testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" testImplementation "org.mockito.kotlin:mockito-kotlin:$testing_kotlin_mockito_version" - testImplementation "org.junit.jupiter:junit-jupiter-engine:5.7.0" + testImplementation "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version" testImplementation project(':mockserver') - testImplementation "org.koin:koin-test:$koin_version" + testImplementation "io.insert-koin:koin-test-junit5:$koin_version" testImplementation "org.skyscreamer:jsonassert:$testing_json_assert_version" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$testing_junit5_version" } \ No newline at end of file diff --git a/network/src/main/java/org/fnives/test/showcase/network/di/createNetworkmodules.kt b/network/src/main/java/org/fnives/test/showcase/network/di/createNetworkmodules.kt index 722cc4c..b4b0e8b 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/di/createNetworkmodules.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/di/createNetworkmodules.kt @@ -50,7 +50,7 @@ private fun contentModule() = module { private fun sessionlessNetworkingModule(baseUrl: BaseUrl, enableLogging: Boolean) = module { factory { MoshiConverterFactory.create() } - single(qualifier = sessionless, override = true) { + single(qualifier = sessionless) { OkHttpClient.Builder() .addInterceptor(PlatformInterceptor()) .setupLogging(enableLogging) diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/CodeKataLoginRemoteSourceTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/CodeKataLoginRemoteSourceTest.kt index c26664d..1baa1d1 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/CodeKataLoginRemoteSourceTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/CodeKataLoginRemoteSourceTest.kt @@ -22,6 +22,7 @@ import org.skyscreamer.jsonassert.JSONCompareMode import java.io.BufferedReader import java.io.InputStreamReader +@Disabled("CodeKata") class CodeKataLoginRemoteSourceTest { @BeforeEach diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt index 93d3408..a977bf4 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt @@ -17,6 +17,7 @@ import org.koin.test.KoinTest import org.koin.test.inject import org.mockito.kotlin.* +@Disabled("CodeKata") class CodeKataSessionExpirationTest : KoinTest { private val sut by inject() From 516b097e9e51f13bc635cf870bc798b15e6a2a3f Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Sat, 18 Sep 2021 16:56:18 +0300 Subject: [PATCH 2/3] Add pipeline: codeAnalysis, tests and clean up gradle --- .github/dependabot.yml | 10 + .github/workflows/pull-request-jobs.yml | 71 +++ app/build.gradle | 30 +- app/src/main/AndroidManifest.xml | 3 +- .../FavouriteContentLocalStorageImplTest.kt | 30 +- .../showcase/ui/auth/CodeKataAuthViewModel.kt | 6 +- .../ui/splash/CodeKataSplashViewModelTest.kt | 14 +- build.gradle | 43 +- core/build.gradle | 9 - .../content/CodeKataContentRepositoryTest.kt | 35 +- .../core/content/ContentRepositoryTest.kt | 75 +-- .../login/CodeKataSecondLoginUseCaseTest.kt | 17 +- .../showcase/core/login/LoginUseCaseTest.kt | 10 +- ...deKataFirstSessionExpirationAdapterTest.kt | 8 +- detekt/detekt.yml | 450 +++++++++++++----- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlescripts/detekt.config.gradle | 24 + gradlescripts/ktlint.gradle | 3 + gradlescripts/lint.gradle | 34 ++ gradlescripts/testoptions.gradle | 44 ++ model/build.gradle | 8 +- network/build.gradle | 9 - .../auth/CodeKataLoginRemoteSourceTest.kt | 36 +- .../network/auth/LoginRemoteSourceTest.kt | 13 +- .../content/CodeKataSessionExpirationTest.kt | 27 +- .../network/content/SessionExpirationTest.kt | 62 +-- 27 files changed, 681 insertions(+), 394 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/pull-request-jobs.yml create mode 100644 gradlescripts/detekt.config.gradle create mode 100644 gradlescripts/ktlint.gradle create mode 100644 gradlescripts/lint.gradle create mode 100644 gradlescripts/testoptions.gradle diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..591f8b0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: gradle + directory: "/" + target-branch: "develop" + schedule: + interval: "weekly" + day: "monday" + time: "12:00" + open-pull-requests-limit: 15 \ No newline at end of file diff --git a/.github/workflows/pull-request-jobs.yml b/.github/workflows/pull-request-jobs.yml new file mode 100644 index 0000000..7aa4d80 --- /dev/null +++ b/.github/workflows/pull-request-jobs.yml @@ -0,0 +1,71 @@ +name: Verify Pull request is publishable + +on: + pull_request: + branches: + - develop + +env: + GITHUB_USERNAME: "fknives" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + run-code-analysis: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: 'adopt' + java-version: '11' + - name: Run clean + run: ./gradlew clean + - name: Run detekt + run: ./gradlew detekt + - name: Upload Detekt Results + - uses: actions/upload-artifact@v2 + with: + name: Detekt Results + path: build/reports/detekt/detekt.html + retention-days: 1 + - name: Run ktlint + run: ./gradlew ktlintCheck + - name: Upload ktLint Results + - uses: actions/upload-artifact@v2 + with: + name: ktLint Results + path: ./**/build/reports/ktlint/**/*ktlint*Check.txt + retention-days: 1 + - name: Run Lint + run: ./gradlew lint + - name: Upload Lint Results + - uses: actions/upload-artifact@v2 + with: + name: Lint Results + path: ./**/build/reports/*lint-results*.html + retention-days: 1 + + run-tests: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: 'adopt' + java-version: '11' + - name: Run Unit Tests + run: ./gradlew testReleaseUnit + - name: Upload Test Results + - uses: actions/upload-artifact@v2 + with: + name: Unit Test Results + path: ./**/build/reports/tests/**/index.html + retention-days: 1 diff --git a/app/build.gradle b/app/build.gradle index d63bebe..b227cb4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,7 +6,6 @@ plugins { android { compileSdkVersion 30 - buildToolsVersion "30.0.3" defaultConfig { applicationId "org.fnives.test.showcase" @@ -25,16 +24,10 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } + buildFeatures { viewBinding true } - kotlinOptions { - jvmTarget = '1.8' - } sourceSets { androidTest { @@ -46,19 +39,6 @@ android { } } - testOptions.unitTests.all { - useJUnitPlatform() - testLogging { - events 'started', 'passed', 'skipped', 'failed' - exceptionFormat "full" - showStandardStreams true - } - } - testOptions { - unitTests { - includeAndroidResources = true - } - } // needed for androidTest packagingOptions { exclude 'META-INF/LGPL2.1' @@ -66,14 +46,6 @@ android { exclude 'META-INF/LICENSE.md' exclude 'META-INF/LICENSE-notice.md' } - - lintOptions { - warningsAsErrors true - abortOnError true - textReport true - ignore 'Overdraw' - textOutput "stdout" - } } afterEvaluate { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1000bd1..09b554f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,7 +14,8 @@ android:name=".TestShowcaseApplication" android:theme="@style/Theme.TestShowCase" tools:ignore="AllowBackup"> - + diff --git a/app/src/robolectricTest/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt b/app/src/robolectricTest/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt index 06d314b..4434ed6 100644 --- a/app/src/robolectricTest/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt +++ b/app/src/robolectricTest/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt @@ -3,14 +3,11 @@ package org.fnives.test.showcase.favourite import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineScope -import kotlinx.coroutines.test.runBlockingTest import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorage import org.fnives.test.showcase.model.content.ContentId import org.fnives.test.showcase.storage.database.DatabaseInitialization @@ -44,25 +41,26 @@ internal class FavouriteContentLocalStorageImplTest : KoinTest { @Test fun GIVEN_content_id_WHEN_added_to_Favourite_THEN_it_can_be_read_out() = runBlocking { - val expected = listOf(ContentId("a")) + val expected = listOf(ContentId("a")) - sut.markAsFavourite(ContentId("a")) - val actual = sut.observeFavourites().first() - - Assert.assertEquals(expected, actual) - } - - @Test - fun GIVEN_content_id_added_WHEN_removed_to_Favourite_THEN_it_no_longer_can_be_read_out() = runBlocking { - val expected = listOf() - sut.markAsFavourite(ContentId("b")) - - sut.deleteAsFavourite(ContentId("b")) + sut.markAsFavourite(ContentId("a")) val actual = sut.observeFavourites().first() Assert.assertEquals(expected, actual) } + @Test + fun GIVEN_content_id_added_WHEN_removed_to_Favourite_THEN_it_no_longer_can_be_read_out() = + runBlocking { + val expected = listOf() + sut.markAsFavourite(ContentId("b")) + + sut.deleteAsFavourite(ContentId("b")) + val actual = sut.observeFavourites().first() + + Assert.assertEquals(expected, actual) + } + @Test fun GIVEN_empty_database_WHILE_observing_content_WHEN_favourite_added_THEN_change_is_emitted() = runBlocking { diff --git a/app/src/test/java/org/fnives/test/showcase/ui/auth/CodeKataAuthViewModel.kt b/app/src/test/java/org/fnives/test/showcase/ui/auth/CodeKataAuthViewModel.kt index 917f67e..1d01ef7 100644 --- a/app/src/test/java/org/fnives/test/showcase/ui/auth/CodeKataAuthViewModel.kt +++ b/app/src/test/java/org/fnives/test/showcase/ui/auth/CodeKataAuthViewModel.kt @@ -27,24 +27,20 @@ class CodeKataAuthViewModel { @DisplayName("GIVEN_initialized_viewModel_WHEN_observed_THEN_loading_false_other_fields_are_empty") @Test fun initialSetup() { - } @DisplayName("GIVEN_password_text_WHEN_onPasswordChanged_is_called_THEN_password_livedata_is_updated") @Test fun whenPasswordChangedLiveDataIsUpdated() { - } @DisplayName("GIVEN_username_text_WHEN_onUsernameChanged_is_called_THEN_username_livedata_is_updated") @Test fun whenUsernameChangedLiveDataIsUpdated() { - } @DisplayName("GIVEN_no_password_or_username_WHEN_login_is_Called_THEN_empty_credentials_are_used_in_usecase") @Test fun noPasswordUsesEmptyStringInLoginUseCase() { - } -} \ No newline at end of file +} diff --git a/app/src/test/java/org/fnives/test/showcase/ui/splash/CodeKataSplashViewModelTest.kt b/app/src/test/java/org/fnives/test/showcase/ui/splash/CodeKataSplashViewModelTest.kt index 4e6738d..475bb22 100644 --- a/app/src/test/java/org/fnives/test/showcase/ui/splash/CodeKataSplashViewModelTest.kt +++ b/app/src/test/java/org/fnives/test/showcase/ui/splash/CodeKataSplashViewModelTest.kt @@ -1,41 +1,29 @@ package org.fnives.test.showcase.ui.splash -import org.fnives.test.showcase.core.login.IsUserLoggedInUseCase -import org.fnives.test.showcase.testutils.InstantExecutorExtension -import org.fnives.test.showcase.testutils.TestMainDispatcher -import org.fnives.test.showcase.ui.shared.Event import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.mock -import org.mockito.kotlin.whenever @Disabled("CodeKata") internal class CodeKataSplashViewModelTest { @BeforeEach fun setUp() { - } @DisplayName("GIVEN not logged in user WHEN splash started THEN after half a second navigated to authentication") @Test fun loggedOutUserGoesToAuthentication() { - } @DisplayName("GIVEN logged in user WHEN splash started THEN after half a second navigated to home") @Test fun loggedInUserGoestoHome() { - } @DisplayName("GIVEN not logged in user WHEN splash started THEN before half a second no event is sent") @Test fun withoutEnoughTimeNoNavigationHappens() { - } -} \ No newline at end of file +} diff --git a/build.gradle b/build.gradle index a53abdc..c09cd78 100644 --- a/build.gradle +++ b/build.gradle @@ -1,49 +1,22 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.kotlin_version = "1.5.30" - ext.detekt_version = "1.16.0" + ext.detekt_version = "1.18.1" repositories { mavenCentral() google() maven { url "https://plugins.gradle.org/m2/" } } dependencies { - classpath "com.android.tools.build:gradle:4.1.3" + classpath 'com.android.tools.build:gradle:7.0.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jlleitschuh.gradle:ktlint-gradle:10.0.0" - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.0" } } -//apply plugin: "io.gitlab.arturbosch.detekt" version "$detekt_version" plugins { id "io.gitlab.arturbosch.detekt" version "$detekt_version" } -detekt { - toolVersion = "$detekt_version" - - input = files( - "$projectDir/app/src/main/java", - "$projectDir/core/src/main/java", - "$projectDir/mockserver/src/main/java", - "$projectDir/model/src/main/java", - "$projectDir/network/src/main/java" - ) - config = files("$projectDir/detekt/detekt.yml") - baseline = file("$projectDir/detekt/baseline.xml") - reports { - txt { - enabled = true - destination = file("build/reports/detekt.txt") - } - html { - enabled = true - destination = file("build/reports/detekt.html") - } - } -} allprojects { repositories { @@ -52,10 +25,6 @@ allprojects { } } -subprojects { - apply plugin: "org.jlleitschuh.gradle.ktlint" -} - task clean(type: Delete) { delete rootProject.buildDir } @@ -70,4 +39,8 @@ task androidTests(dependsOn: "app:connectedAndroidTest"){ description = 'Run all Android tests' } -apply from: 'gradlescripts/versions.gradle' \ No newline at end of file +apply from: 'gradlescripts/versions.gradle' +apply from: 'gradlescripts/detekt.config.gradle' +apply from: 'gradlescripts/ktlint.gradle' +apply from: 'gradlescripts/lint.gradle' +apply from: 'gradlescripts/testoptions.gradle' \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle index 5d442cc..745175d 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -8,15 +8,6 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -test { - useJUnitPlatform() - testLogging { - events 'started', 'passed', 'skipped', 'failed' - exceptionFormat "full" - showStandardStreams true - } -} - compileKotlin { kotlinOptions { freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" diff --git a/core/src/test/java/org/fnives/test/showcase/core/content/CodeKataContentRepositoryTest.kt b/core/src/test/java/org/fnives/test/showcase/core/content/CodeKataContentRepositoryTest.kt index c8f74fe..fb70a9a 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/content/CodeKataContentRepositoryTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/content/CodeKataContentRepositoryTest.kt @@ -1,75 +1,50 @@ package org.fnives.test.showcase.core.content -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.async -import kotlinx.coroutines.flow.take -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.runBlockingTest -import org.fnives.test.showcase.core.shared.UnexpectedException -import org.fnives.test.showcase.model.content.Content -import org.fnives.test.showcase.model.content.ContentId -import org.fnives.test.showcase.model.content.ImageUrl -import org.fnives.test.showcase.model.shared.Resource -import org.fnives.test.showcase.network.content.ContentRemoteSource -import org.junit.jupiter.api.* -import org.mockito.kotlin.doAnswer -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.doSuspendableAnswer -import org.mockito.kotlin.doThrow -import org.mockito.kotlin.mock -import org.mockito.kotlin.times -import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyNoMoreInteractions -import org.mockito.kotlin.whenever +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test @Disabled("CodeKata") class CodeKataContentRepositoryTest { @BeforeEach fun setUp() { - } @DisplayName("GIVEN no interaction THEN remote source is not called") @Test fun fetchingIsLazy() { - } @DisplayName("GIVEN content response WHEN content observed THEN loading AND data is returned") @Test fun happyFlow() = runBlockingTest { - } @DisplayName("GIVEN content error WHEN content observed THEN loading AND data is returned") @Test fun errorFlow() = runBlockingTest { - } @DisplayName("GIVEN saved cache WHEN collected THEN cache is returned") @Test fun verifyCaching() = runBlockingTest { - } @DisplayName("GIVEN no response from remote source WHEN content observed THEN loading is returned") @Test fun loadingIsShownBeforeTheRequestIsReturned() = runBlockingTest { - } @DisplayName("GIVEN content response THEN error WHEN fetched THEN returned states are loading data loading error") @Test fun whenFetchingRequestIsCalledAgain() = runBlockingTest { - } @DisplayName("GIVEN content response THEN error WHEN fetched THEN only 4 items are emitted") @Test fun noAdditionalItemsEmitted() { - } -} \ No newline at end of file +} diff --git a/core/src/test/java/org/fnives/test/showcase/core/content/ContentRepositoryTest.kt b/core/src/test/java/org/fnives/test/showcase/core/content/ContentRepositoryTest.kt index 7398fd4..eaeb901 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/content/ContentRepositoryTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/content/ContentRepositoryTest.kt @@ -16,7 +16,15 @@ import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test -import org.mockito.kotlin.* +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doSuspendableAnswer +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever @Suppress("TestFunctionName") internal class ContentRepositoryTest { @@ -43,10 +51,19 @@ internal class ContentRepositoryTest { @Test fun happyFlow() = runBlockingTest { val expected = listOf( - Resource.Loading(), - Resource.Success(listOf(Content(ContentId("a"), "", "", ImageUrl("")))) + Resource.Loading(), + Resource.Success(listOf(Content(ContentId("a"), "", "", ImageUrl("")))) + ) + whenever(mockContentRemoteSource.get()).doReturn( + listOf( + Content( + ContentId("a"), + "", + "", + ImageUrl("") + ) + ) ) - whenever(mockContentRemoteSource.get()).doReturn(listOf(Content(ContentId("a"), "", "", ImageUrl("")))) val actual = sut.contents.take(2).toList() @@ -58,8 +75,8 @@ internal class ContentRepositoryTest { fun errorFlow() = runBlockingTest { val exception = RuntimeException() val expected = listOf( - Resource.Loading(), - Resource.Error>(UnexpectedException(exception)) + Resource.Loading(), + Resource.Error>(UnexpectedException(exception)) ) whenever(mockContentRemoteSource.get()).doThrow(exception) @@ -101,27 +118,27 @@ internal class ContentRepositoryTest { @DisplayName("GIVEN content response THEN error WHEN fetched THEN returned states are loading data loading error") @Test fun whenFetchingRequestIsCalledAgain() = - runBlockingTest(testDispatcher) { - val exception = RuntimeException() - val expected = listOf( - Resource.Loading(), - Resource.Success(emptyList()), - Resource.Loading(), - Resource.Error>(UnexpectedException(exception)) - ) - var first = true - whenever(mockContentRemoteSource.get()).doAnswer { - if (first) emptyList().also { first = false } else throw exception - } - - val actual = async(testDispatcher) { sut.contents.take(4).toList() } - testDispatcher.advanceUntilIdle() - sut.fetch() - testDispatcher.advanceUntilIdle() - - Assertions.assertEquals(expected, actual.await()) + runBlockingTest(testDispatcher) { + val exception = RuntimeException() + val expected = listOf( + Resource.Loading(), + Resource.Success(emptyList()), + Resource.Loading(), + Resource.Error>(UnexpectedException(exception)) + ) + var first = true + whenever(mockContentRemoteSource.get()).doAnswer { + if (first) emptyList().also { first = false } else throw exception } + val actual = async(testDispatcher) { sut.contents.take(4).toList() } + testDispatcher.advanceUntilIdle() + sut.fetch() + testDispatcher.advanceUntilIdle() + + Assertions.assertEquals(expected, actual.await()) + } + @DisplayName("GIVEN content response THEN error WHEN fetched THEN only 4 items are emitted") @Test fun noAdditionalItemsEmitted() { @@ -129,10 +146,10 @@ internal class ContentRepositoryTest { runBlockingTest(testDispatcher) { val exception = RuntimeException() val expected = listOf( - Resource.Loading(), - Resource.Success(emptyList()), - Resource.Loading(), - Resource.Error>(UnexpectedException(exception)) + Resource.Loading(), + Resource.Success(emptyList()), + Resource.Loading(), + Resource.Error>(UnexpectedException(exception)) ) var first = true whenever(mockContentRemoteSource.get()).doAnswer { diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/CodeKataSecondLoginUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/login/CodeKataSecondLoginUseCaseTest.kt index 3bef542..a689882 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/login/CodeKataSecondLoginUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/login/CodeKataSecondLoginUseCaseTest.kt @@ -1,16 +1,9 @@ package org.fnives.test.showcase.core.login -import kotlinx.coroutines.test.runBlockingTest -import org.fnives.test.showcase.core.shared.UnexpectedException -import org.fnives.test.showcase.core.storage.UserDataLocalStorage -import org.fnives.test.showcase.model.auth.LoginCredentials -import org.fnives.test.showcase.model.auth.LoginStatus -import org.fnives.test.showcase.model.session.Session -import org.fnives.test.showcase.model.shared.Answer -import org.fnives.test.showcase.network.auth.LoginRemoteSource -import org.fnives.test.showcase.network.auth.model.LoginStatusResponses -import org.junit.jupiter.api.* -import org.mockito.kotlin.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test @Disabled("CodeKata") class CodeKataSecondLoginUseCaseTest { @@ -49,4 +42,4 @@ class CodeKataSecondLoginUseCaseTest { fun invalidResponseResultsInErrorReturned() { TODO() } -} \ No newline at end of file +} diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/LoginUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/login/LoginUseCaseTest.kt index b5050ac..142593f 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/login/LoginUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/login/LoginUseCaseTest.kt @@ -13,8 +13,14 @@ import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test -import org.mockito.kotlin.* -import java.util.stream.Stream +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.whenever @Suppress("TestFunctionName") internal class LoginUseCaseTest { diff --git a/core/src/test/java/org/fnives/test/showcase/core/session/CodeKataFirstSessionExpirationAdapterTest.kt b/core/src/test/java/org/fnives/test/showcase/core/session/CodeKataFirstSessionExpirationAdapterTest.kt index 686fa7f..6454d59 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/session/CodeKataFirstSessionExpirationAdapterTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/session/CodeKataFirstSessionExpirationAdapterTest.kt @@ -1,12 +1,6 @@ package org.fnives.test.showcase.core.session -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.mockito.kotlin.* @Disabled("CodeKata") -class CodeKataFirstSessionExpirationAdapterTest { - -} \ No newline at end of file +class CodeKataFirstSessionExpirationAdapterTest diff --git a/detekt/detekt.yml b/detekt/detekt.yml index 3e05232..0fd7e71 100644 --- a/detekt/detekt.yml +++ b/detekt/detekt.yml @@ -1,56 +1,79 @@ build: - maxIssues: 15 + maxIssues: 0 + excludeCorrectable: false weights: - # complexity: 2 - # LongParameterList: 1 - # style: 1 - # comments: 1 + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 config: validation: true - # when writing own rules with new properties, exclude the property path e.g.: "my_rule_set,.*>.*>[my_property]" - excludes: "" + warningsAsErrors: false + # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' + excludes: '' processors: active: true exclude: - # - 'DetektProgressListener' + - 'DetektProgressListener' + # - 'KtFileCountProcessor' + # - 'PackageCountProcessor' + # - 'ClassCountProcessor' # - 'FunctionCountProcessor' # - 'PropertyCountProcessor' - # - 'ClassCountProcessor' - # - 'PackageCountProcessor' - # - 'KtFileCountProcessor' + # - 'ProjectComplexityProcessor' + # - 'ProjectCognitiveComplexityProcessor' + # - 'ProjectLLOCProcessor' + # - 'ProjectCLOCProcessor' + # - 'ProjectLOCProcessor' + # - 'ProjectSLOCProcessor' + # - 'LicenseHeaderLoaderExtension' console-reports: active: true exclude: - # - 'ProjectStatisticsReport' - # - 'ComplexityReport' - # - 'NotificationReport' - # - 'FindingsReport' - - 'FileBasedFindingsReport' - # - 'BuildFailureReport' + - 'ProjectStatisticsReport' + - 'ComplexityReport' + - 'NotificationReport' + # - 'FindingsReport' + - 'FileBasedFindingsReport' + +output-reports: + active: true + exclude: + # - 'TxtOutputReport' + # - 'XmlOutputReport' + # - 'HtmlOutputReport' comments: active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + AbsentOrWrongFileLicense: + active: false + licenseTemplateFile: 'license.template' + licenseTemplateIsRegex: false CommentOverPrivateFunction: active: false CommentOverPrivateProperty: active: false + DeprecatedBlockTag: + active: false EndOfSentenceFormat: active: false - endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!:]$) + endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' UndocumentedPublicClass: active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] searchInNestedClass: true searchInInnerClass: true searchInInnerObject: true searchInInnerInterface: true UndocumentedPublicFunction: active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] UndocumentedPublicProperty: active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] complexity: active: true @@ -61,57 +84,86 @@ complexity: active: false threshold: 10 includeStaticDeclarations: false + includePrivateDeclarations: false ComplexMethod: active: true - threshold: 25 + threshold: 15 ignoreSingleWhenExpression: false ignoreSimpleWhenEntries: false ignoreNestingFunctions: false - nestingFunctions: run,let,apply,with,also,use,forEach,isNotNull,ifNull + nestingFunctions: + - 'also' + - 'apply' + - 'forEach' + - 'isNotNull' + - 'ifNull' + - 'let' + - 'run' + - 'use' + - 'with' LabeledExpression: active: false - ignoredLabels: "" + ignoredLabels: [] LargeClass: active: true threshold: 600 LongMethod: active: true - threshold: 200 + threshold: 60 + ignoreAnnotated: [] LongParameterList: active: true functionThreshold: 6 - constructorThreshold: 6 + constructorThreshold: 7 ignoreDefaultParameters: false + ignoreDataClasses: true + ignoreAnnotated: [] MethodOverloading: active: false threshold: 6 + NamedArguments: + active: false + threshold: 3 NestedBlockDepth: active: true - threshold: 100 + threshold: 4 + ReplaceSafeCallChainWithRun: + active: false StringLiteralDuplication: active: false - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] threshold: 3 ignoreAnnotation: true excludeStringsWithLessThan5Characters: true ignoreStringsRegex: '$^' TooManyFunctions: active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - thresholdInFiles: 25 - thresholdInClasses: 25 - thresholdInInterfaces: 25 - thresholdInObjects: 25 - thresholdInEnums: 25 + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + thresholdInFiles: 11 + thresholdInClasses: 11 + thresholdInInterfaces: 11 + thresholdInObjects: 11 + thresholdInEnums: 11 ignoreDeprecated: false ignorePrivate: false ignoreOverridden: false +coroutines: + active: true + GlobalCoroutineUsage: + active: false + RedundantSuspendModifier: + active: false + SleepInsteadOfDelay: + active: false + SuspendFunWithFlowReturnType: + active: false + empty-blocks: active: true EmptyCatchBlock: active: true - allowedExceptionNameRegex: "^(_|(ignore|expected).*)" + allowedExceptionNameRegex: '_|(ignore|expected).*' EmptyClassBlock: active: true EmptyDefaultConstructor: @@ -135,6 +187,8 @@ empty-blocks: active: true EmptySecondaryConstructor: active: true + EmptyTryBlock: + active: true EmptyWhenBlock: active: true EmptyWhileBlock: @@ -143,53 +197,73 @@ empty-blocks: exceptions: active: true ExceptionRaisedInUnexpectedLocation: - active: false - methodNames: 'toString,hashCode,equals,finalize' + active: true + methodNames: + - 'equals' + - 'finalize' + - 'hashCode' + - 'toString' InstanceOfCheckForException: active: false - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] NotImplementedDeclaration: active: false + ObjectExtendsThrowable: + active: false PrintStackTrace: - active: false + active: true RethrowCaughtException: - active: false + active: true ReturnFromFinally: - active: false + active: true ignoreLabeled: false SwallowedException: - active: false - ignoredExceptionTypes: 'InterruptedException,NumberFormatException,ParseException,MalformedURLException' - allowedExceptionNameRegex: "^(_|(ignore|expected).*)" + active: true + ignoredExceptionTypes: + - 'InterruptedException' + - 'MalformedURLException' + - 'NumberFormatException' + - 'ParseException' + allowedExceptionNameRegex: '_|(ignore|expected).*' ThrowingExceptionFromFinally: - active: false + active: true ThrowingExceptionInMain: active: false ThrowingExceptionsWithoutMessageOrCause: active: true - exceptions: 'IllegalArgumentException,IllegalStateException,IOException' + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + exceptions: + - 'ArrayIndexOutOfBoundsException' + - 'Exception' + - 'IllegalArgumentException' + - 'IllegalMonitorStateException' + - 'IllegalStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' ThrowingNewInstanceOfSameException: active: true TooGenericExceptionCaught: active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] exceptionNames: - - ArrayIndexOutOfBoundsException - - Error - - Exception - - IllegalMonitorStateException - - NullPointerException - - IndexOutOfBoundsException - - RuntimeException - - Throwable - allowedExceptionNameRegex: "^(_|(ignore|expected).*)" + - 'ArrayIndexOutOfBoundsException' + - 'Error' + - 'Exception' + - 'IllegalMonitorStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + allowedExceptionNameRegex: '_|(ignore|expected).*' TooGenericExceptionThrown: active: true exceptionNames: - - Error - - Exception - - Throwable - - RuntimeException + - 'Error' + - 'Exception' + - 'RuntimeException' + - 'Throwable' formatting: active: true @@ -198,28 +272,42 @@ formatting: AnnotationOnSeparateLine: active: false autoCorrect: true + AnnotationSpacing: + active: false + autoCorrect: true + ArgumentListWrapping: + active: false + autoCorrect: true + indentSize: 4 + maxLineLength: 120 ChainWrapping: active: true autoCorrect: true CommentSpacing: active: true autoCorrect: true + EnumEntryNameCase: + active: false + autoCorrect: true Filename: active: true FinalNewline: active: true autoCorrect: true + insertFinalNewLine: true ImportOrdering: - active: true + active: false autoCorrect: true + layout: '*,java.**,javax.**,kotlin.**,^' Indentation: - active: true + active: false autoCorrect: true indentSize: 4 continuationIndentSize: 4 MaximumLineLength: active: true - maxLineLength: 250 + maxLineLength: 120 + ignoreBackTickedIdentifier: false ModifierOrdering: active: true autoCorrect: true @@ -235,6 +323,9 @@ formatting: NoEmptyClassBody: active: true autoCorrect: true + NoEmptyFirstLineInMethodBlock: + active: false + autoCorrect: true NoLineBreakAfterElse: active: true autoCorrect: true @@ -265,6 +356,10 @@ formatting: active: true autoCorrect: true indentSize: 4 + maxLineLength: 120 + SpacingAroundAngleBrackets: + active: false + autoCorrect: true SpacingAroundColon: active: true autoCorrect: true @@ -277,6 +372,9 @@ formatting: SpacingAroundDot: active: true autoCorrect: true + SpacingAroundDoubleColon: + active: false + autoCorrect: true SpacingAroundKeyword: active: true autoCorrect: true @@ -289,85 +387,108 @@ formatting: SpacingAroundRangeOperator: active: true autoCorrect: true + SpacingAroundUnaryOperator: + active: false + autoCorrect: true + SpacingBetweenDeclarationsWithAnnotations: + active: false + autoCorrect: true + SpacingBetweenDeclarationsWithComments: + active: false + autoCorrect: true StringTemplate: active: true autoCorrect: true naming: active: true + BooleanPropertyNaming: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + allowedPattern: '^(is|has|are)' ClassNaming: active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - classPattern: '[A-Z$][a-zA-Z0-9$]*' + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + classPattern: '[A-Z][a-zA-Z0-9]*' ConstructorParameterNaming: active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] parameterPattern: '[a-z][A-Za-z0-9]*' privateParameterPattern: '[a-z][A-Za-z0-9]*' excludeClassPattern: '$^' + ignoreOverridden: true EnumNaming: active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*' + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' ForbiddenClassName: active: false - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - forbiddenName: '' + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + forbiddenName: [] FunctionMaxLength: active: false - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] maximumFunctionNameLength: 30 FunctionMinLength: active: false - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] minimumFunctionNameLength: 3 FunctionNaming: active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$' + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + functionPattern: '([a-z][a-zA-Z0-9]*)|(`.*`)' excludeClassPattern: '$^' ignoreOverridden: true + ignoreAnnotated: + - 'Composable' FunctionParameterNaming: active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] parameterPattern: '[a-z][A-Za-z0-9]*' excludeClassPattern: '$^' ignoreOverridden: true InvalidPackageDeclaration: active: false + excludes: ['**/*.kts'] rootPackage: '' MatchingDeclarationName: active: true + mustBeFirst: true MemberNameEqualsClassName: active: true ignoreOverridden: true + NoNameShadowing: + active: false + NonBooleanPropertyPrefixedWithIs: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] ObjectPropertyNaming: active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] constantPattern: '[A-Za-z][_A-Za-z0-9]*' propertyPattern: '[A-Za-z][_A-Za-z0-9]*' privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' PackageNaming: - active: false - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$' + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' TopLevelPropertyNaming: active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] constantPattern: '[A-Z][_A-Z0-9]*' propertyPattern: '[A-Za-z][_A-Za-z0-9]*' privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' VariableMaxLength: active: false - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] maximumVariableNameLength: 64 VariableMinLength: active: false - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] minimumVariableNameLength: 1 VariableNaming: active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] variablePattern: '[a-z][A-Za-z0-9]*' privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' excludeClassPattern: '$^' @@ -379,29 +500,52 @@ performance: active: true ForEachOnRange: active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] SpreadOperator: active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] UnnecessaryTemporaryInstantiation: active: true potential-bugs: active: true + AvoidReferentialEquality: + active: false + forbiddenTypePatterns: + - 'kotlin.String' + CastToNullableType: + active: false Deprecation: active: false + DontDowncastCollectionTypes: + active: false + DoubleMutabilityForCollection: + active: false DuplicateCaseInWhenExpression: active: true EqualsAlwaysReturnsTrueOrFalse: active: true EqualsWithHashCodeExist: active: true + ExitOutsideMain: + active: false ExplicitGarbageCollectionCall: active: true HasPlatformType: active: false - ImplicitDefaultLocale: + IgnoredReturnValue: active: false + restrictToAnnotatedMethods: true + returnValueAnnotations: + - '*.CheckResult' + - '*.CheckReturnValue' + ignoreReturnValueAnnotations: + - '*.CanIgnoreReturnValue' + ImplicitDefaultLocale: + active: true + ImplicitUnitReturnType: + active: false + allowExplicitReturnType: true InvalidRange: active: true IteratorHasNextCallsNextMethod: @@ -410,20 +554,33 @@ potential-bugs: active: true LateinitUsage: active: false - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - excludeAnnotatedProperties: "" - ignoreOnClassesPattern: "" + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + excludeAnnotatedProperties: [] + ignoreOnClassesPattern: '' + MapGetWithNotNullAssertionOperator: + active: false MissingWhenCase: active: true + allowElseExpression: true + NullableToStringCall: + active: false RedundantElseInWhen: active: true UnconditionalJumpStatementInLoop: active: false + UnnecessaryNotNullOperator: + active: true + UnnecessarySafeCall: + active: true + UnreachableCatchBlock: + active: false UnreachableCode: active: true UnsafeCallOnNullableType: active: true UnsafeCast: + active: true + UnusedUnaryOperator: active: false UselessPostfixExpression: active: false @@ -432,30 +589,51 @@ potential-bugs: style: active: true + ClassOrdering: + active: false CollapsibleIfStatements: - active: true + active: false DataClassContainsFunctions: - active: true + active: false conversionFunctionPrefix: 'to' DataClassShouldBeImmutable: active: false + DestructuringDeclarationWithTooManyEntries: + active: false + maxDestructuringEntries: 3 EqualsNullCall: active: true EqualsOnSignatureLine: active: false + ExplicitCollectionElementAccessMethod: + active: false ExplicitItLambdaParameter: active: false ExpressionBodySyntax: - active: true + active: false includeLineWrapping: false ForbiddenComment: active: true - values: 'TODO:,FIXME:,STOPSHIP:' - allowedPatterns: "" + values: + - 'FIXME:' + - 'STOPSHIP:' + - 'TODO:' + allowedPatterns: '' ForbiddenImport: active: false - imports: '' - forbiddenPatterns: "" + imports: [] + forbiddenPatterns: '' + ForbiddenMethodCall: + active: false + methods: + - 'kotlin.io.print' + - 'kotlin.io.println' + ForbiddenPublicDataClass: + active: true + excludes: ['**'] + ignorePackages: + - '*.internal' + - '*.internal.*' ForbiddenVoid: active: false ignoreOverridden: false @@ -463,30 +641,44 @@ style: FunctionOnlyReturningConstant: active: true ignoreOverridableFunction: true - excludedFunctions: 'describeContents' - excludeAnnotatedFunction: "dagger.Provides" + ignoreActualFunction: true + excludedFunctions: '' + excludeAnnotatedFunction: + - 'dagger.Provides' LibraryCodeMustSpecifyReturnType: active: true + excludes: ['**'] + LibraryEntitiesShouldNotBePublic: + active: true + excludes: ['**'] LoopWithTooManyJumpStatements: active: true maxJumpCount: 1 MagicNumber: active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - ignoreNumbers: '-1,0,1,2' + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + ignoreNumbers: + - '-1' + - '0' + - '1' + - '2' ignoreHashCodeFunction: true ignorePropertyDeclaration: false + ignoreLocalVariableDeclaration: false ignoreConstantDeclaration: true ignoreCompanionObjectPropertyDeclaration: true ignoreAnnotation: false ignoreNamedArgument: true ignoreEnums: false ignoreRanges: false + ignoreExtensionFunctions: true MandatoryBracesIfStatements: - active: true + active: false + MandatoryBracesLoops: + active: false MaxLineLength: active: true - maxLineLength: 160 #default: 120 + maxLineLength: 120 excludePackageStatements: true excludeImportStatements: true excludeCommentStatements: false @@ -494,12 +686,16 @@ style: active: true ModifierOrder: active: true - NestedClassesVisibility: + MultilineLambdaItParameter: active: false + NestedClassesVisibility: + active: true NewLineAtEndOfFile: active: true NoTabs: active: false + ObjectLiteralToLambda: + active: false OptionalAbstractKeyword: active: true OptionalUnit: @@ -507,29 +703,32 @@ style: OptionalWhenBraces: active: false PreferToOverPairSyntax: - active: true + active: false ProtectedMemberInFinalClass: active: true RedundantExplicitType: - active: true + active: false + RedundantHigherOrderMapUsage: + active: false RedundantVisibilityModifierRule: active: false ReturnCount: active: true - max: 5 - excludedFunctions: "equals" + max: 2 + excludedFunctions: 'equals' excludeLabeled: false excludeReturnFromLambda: true excludeGuardClauses: false SafeCast: active: true SerialVersionUIDInSerializableClass: - active: false + active: true SpacingBetweenPackageAndImports: active: false ThrowsCount: active: true - max: 10 + max: 2 + excludeGuardClauses: false TrailingWhitespace: active: false UnderscoresInNumericLiterals: @@ -537,43 +736,62 @@ style: acceptableDecimalLength: 5 UnnecessaryAbstractClass: active: true - excludeAnnotatedClasses: "dagger.Module" + excludeAnnotatedClasses: + - 'dagger.Module' + UnnecessaryAnnotationUseSiteTarget: + active: false UnnecessaryApply: active: true + UnnecessaryFilter: + active: false UnnecessaryInheritance: active: true UnnecessaryLet: - active: true + active: false UnnecessaryParentheses: - active: true + active: false UntilInsteadOfRangeTo: active: false UnusedImports: - active: true + active: false UnusedPrivateClass: active: true UnusedPrivateMember: active: true - allowedNames: "(_|ignored|expected|serialVersionUID)" + allowedNames: '(_|ignored|expected|serialVersionUID)' UseArrayLiteralsInAnnotations: active: false + UseCheckNotNull: + active: false UseCheckOrError: active: false UseDataClass: active: false - excludeAnnotatedClasses: "" + excludeAnnotatedClasses: [] allowVars: false + UseEmptyCounterpart: + active: false + UseIfEmptyOrIfBlank: + active: false UseIfInsteadOfWhen: active: false + UseIsNullOrEmpty: + active: false + UseOrEmpty: + active: false UseRequire: active: false + UseRequireNotNull: + active: false UselessCallOnNotNull: active: true UtilityClassWithPublicConstructor: active: true VarCouldBeVal: - active: false + active: true WildcardImport: active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - excludeImports: 'java.util.*,kotlinx.android.synthetic.*' \ No newline at end of file + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + excludeImports: + - 'java.util.*' + - 'kotlinx.android.synthetic.*' diff --git a/gradle.properties b/gradle.properties index f9842bc..349b5c9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,6 +17,6 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true -android.jetifier.blacklist=bcprov-jdk15on +android.jetifier.ignorelist=bcprov-jdk15on # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6975649..4ee106d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip diff --git a/gradlescripts/detekt.config.gradle b/gradlescripts/detekt.config.gradle new file mode 100644 index 0000000..3e62590 --- /dev/null +++ b/gradlescripts/detekt.config.gradle @@ -0,0 +1,24 @@ + +detekt { + toolVersion = "$detekt_version" + + source = files( + "$projectDir/app/src/main/java", + "$projectDir/core/src/main/java", + "$projectDir/mockserver/src/main/java", + "$projectDir/model/src/main/java", + "$projectDir/network/src/main/java" + ) + config = files("$projectDir/detekt/detekt.yml") + baseline = file("$projectDir/detekt/baseline.xml") + reports { + txt { + enabled = true + destination = file("build/reports/detekt.txt") + } + html { + enabled = true + destination = file("build/reports/detekt.html") + } + } +} \ No newline at end of file diff --git a/gradlescripts/ktlint.gradle b/gradlescripts/ktlint.gradle new file mode 100644 index 0000000..f61347d --- /dev/null +++ b/gradlescripts/ktlint.gradle @@ -0,0 +1,3 @@ +subprojects { + apply plugin: "org.jlleitschuh.gradle.ktlint" +} \ No newline at end of file diff --git a/gradlescripts/lint.gradle b/gradlescripts/lint.gradle new file mode 100644 index 0000000..d906414 --- /dev/null +++ b/gradlescripts/lint.gradle @@ -0,0 +1,34 @@ +subprojects { module -> + plugins.withType(JavaPlugin).whenPluginAdded { + configure(module) { + apply plugin: "com.android.lint" + lintOptions { + warningsAsErrors true + abortOnError true + textReport true + ignore 'Overdraw' + textOutput "stdout" + } + } + } + + plugins.withId("com.android.application") { + module.android.lintOptions { + warningsAsErrors true + abortOnError true + textReport true + ignore 'Overdraw' + textOutput "stdout" + } + } + + plugins.withId("com.android.library") { + module.android.lintOptions { + warningsAsErrors true + abortOnError true + textReport true + ignore 'Overdraw' + textOutput "stdout" + } + } +} \ No newline at end of file diff --git a/gradlescripts/testoptions.gradle b/gradlescripts/testoptions.gradle new file mode 100644 index 0000000..c7b068b --- /dev/null +++ b/gradlescripts/testoptions.gradle @@ -0,0 +1,44 @@ +subprojects { module -> + plugins.withType(JavaPlugin).whenPluginAdded { + module.test { + useJUnitPlatform() + testLogging { + events 'started', 'passed', 'skipped', 'failed' + exceptionFormat "full" + showStandardStreams true + } + } + } + + plugins.withId("com.android.application") { + module.android.testOptions.unitTests.all { + useJUnitPlatform() + testLogging { + events 'started', 'passed', 'skipped', 'failed' + exceptionFormat "full" + showStandardStreams true + } + } + module.android.testOptions { + unitTests { + includeAndroidResources = true + } + } + } + + plugins.withId("com.android.library") { + module.android.testOptions.unitTests.all { + useJUnitPlatform() + testLogging { + events 'started', 'passed', 'skipped', 'failed' + exceptionFormat "full" + showStandardStreams true + } + } + module.android.testOptions { + unitTests { + includeAndroidResources = true + } + } + } +} \ No newline at end of file diff --git a/model/build.gradle b/model/build.gradle index e30cb2f..a1a673c 100644 --- a/model/build.gradle +++ b/model/build.gradle @@ -6,10 +6,4 @@ plugins { java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 -} - -compileKotlin { - kotlinOptions { - freeCompilerArgs += "-Xinline-classes" - } -} +} \ No newline at end of file diff --git a/network/build.gradle b/network/build.gradle index d49020e..fc17ae7 100644 --- a/network/build.gradle +++ b/network/build.gradle @@ -9,15 +9,6 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -test { - useJUnitPlatform() - testLogging { - events 'started', 'passed', 'skipped', 'failed' - exceptionFormat "full" - showStandardStreams true - } -} - dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/CodeKataLoginRemoteSourceTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/CodeKataLoginRemoteSourceTest.kt index 1baa1d1..843e61b 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/CodeKataLoginRemoteSourceTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/CodeKataLoginRemoteSourceTest.kt @@ -1,24 +1,11 @@ package org.fnives.test.showcase.network.auth import kotlinx.coroutines.runBlocking -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer -import org.fnives.test.showcase.model.auth.LoginCredentials -import org.fnives.test.showcase.model.network.BaseUrl -import org.fnives.test.showcase.model.session.Session -import org.fnives.test.showcase.network.auth.model.LoginStatusResponses -import org.fnives.test.showcase.network.di.createNetworkModules -import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage -import org.fnives.test.showcase.network.shared.exceptions.NetworkException -import org.fnives.test.showcase.network.shared.exceptions.ParsingException -import org.junit.jupiter.api.* -import org.koin.core.context.startKoin -import org.koin.core.context.stopKoin -import org.koin.test.KoinTest -import org.koin.test.inject -import org.mockito.kotlin.mock -import org.skyscreamer.jsonassert.JSONAssert -import org.skyscreamer.jsonassert.JSONCompareMode +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test import java.io.BufferedReader import java.io.InputStreamReader @@ -27,54 +14,46 @@ class CodeKataLoginRemoteSourceTest { @BeforeEach fun setUp() { - } @AfterEach fun tearDown() { - } @DisplayName("GIVEN successful response WHEN request is fired THEN login status success is returned") @Test fun successResponseIsParsedProperly() = runBlocking { - } @DisplayName("GIVEN successful response WHEN request is fired THEN the request is setup properly") @Test fun requestProperlySetup() = runBlocking { - } @DisplayName("GIVEN bad request response WHEN request is fired THEN login status invalid credentials is returned") @Test fun badRequestMeansInvalidCredentials() = runBlocking { - } @DisplayName("GIVEN_internal_error_response_WHEN_request_is_fired_THEN_network_exception_is_thrown") @Test fun genericErrorMeansNetworkError() { - } @DisplayName("GIVEN invalid json response WHEN request is fired THEN network exception is thrown") @Test fun invalidJsonMeansParsingException() { - } @DisplayName("GIVEN malformed json response WHEN request is fired THEN network exception is thrown") @Test fun malformedJsonMeansParsingException() { - } companion object { internal fun Any.readResourceFile(filePath: String): String = try { BufferedReader(InputStreamReader(this.javaClass.classLoader.getResourceAsStream(filePath)!!)) - .readLines().joinToString("\n") + .readLines().joinToString("\n") } catch (nullPointerException: NullPointerException) { throw IllegalArgumentException("$filePath file not found!", nullPointerException) } @@ -87,6 +66,5 @@ class CodeKataLoginRemoteSourceTest { } while (true) } } - } -} \ No newline at end of file +} diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt index 0258bf0..cd586bd 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/LoginRemoteSourceTest.kt @@ -12,7 +12,11 @@ import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage import org.fnives.test.showcase.network.shared.MockServerScenarioSetupExtensions import org.fnives.test.showcase.network.shared.exceptions.NetworkException import org.fnives.test.showcase.network.shared.exceptions.ParsingException -import org.junit.jupiter.api.* +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension import org.koin.core.context.startKoin import org.koin.core.context.stopKoin @@ -26,6 +30,7 @@ import org.skyscreamer.jsonassert.JSONCompareMode class LoginRemoteSourceTest : KoinTest { private val sut by inject() + @RegisterExtension @JvmField val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions() @@ -76,7 +81,11 @@ class LoginRemoteSourceTest : KoinTest { Assertions.assertEquals(null, request.getHeader("Authorization")) Assertions.assertEquals("/login", request.path) val loginRequest = createExpectedLoginRequestJson("a", "b") - JSONAssert.assertEquals(loginRequest, request.body.readUtf8(), JSONCompareMode.NON_EXTENSIBLE) + JSONAssert.assertEquals( + loginRequest, + request.body.readUtf8(), + JSONCompareMode.NON_EXTENSIBLE + ) } @DisplayName("GIVEN bad request response WHEN request is fired THEN login status invalid credentials is returned") diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt index a977bf4..eb2f310 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt @@ -1,21 +1,21 @@ package org.fnives.test.showcase.network.content import kotlinx.coroutines.runBlocking -import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import org.fnives.test.showcase.model.network.BaseUrl -import org.fnives.test.showcase.model.session.Session -import org.fnives.test.showcase.network.auth.CodeKataLoginRemoteSourceTest.Companion.readResourceFile import org.fnives.test.showcase.network.di.createNetworkModules import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage -import org.fnives.test.showcase.network.shared.exceptions.NetworkException -import org.junit.jupiter.api.* +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test import org.koin.core.context.startKoin import org.koin.core.context.stopKoin import org.koin.test.KoinTest import org.koin.test.inject -import org.mockito.kotlin.* +import org.mockito.kotlin.mock @Disabled("CodeKata") class CodeKataSessionExpirationTest : KoinTest { @@ -33,12 +33,12 @@ class CodeKataSessionExpirationTest : KoinTest { mockNetworkSessionExpirationListener = mock() startKoin { modules( - createNetworkModules( - baseUrl = BaseUrl(mockWebServer.url("mockserver/").toString()), - enableLogging = true, - networkSessionExpirationListenerProvider = { mockNetworkSessionExpirationListener }, - networkSessionLocalStorageProvider = { mockNetworkSessionLocalStorage } - ).toList() + createNetworkModules( + baseUrl = BaseUrl(mockWebServer.url("mockserver/").toString()), + enableLogging = true, + networkSessionExpirationListenerProvider = { mockNetworkSessionExpirationListener }, + networkSessionLocalStorageProvider = { mockNetworkSessionLocalStorage } + ).toList() ) } } @@ -57,6 +57,5 @@ class CodeKataSessionExpirationTest : KoinTest { @DisplayName("GIVEN 401 THEN failing refresh WHEN content requested THE error is returned and callback is Called") @Test fun failingRefreshResultsInSessionExpiration() = runBlocking { - } -} \ No newline at end of file +} diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt index e63924d..0f80d3e 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/SessionExpirationTest.kt @@ -11,7 +11,11 @@ import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage import org.fnives.test.showcase.network.shared.MockServerScenarioSetupExtensions import org.fnives.test.showcase.network.shared.exceptions.NetworkException -import org.junit.jupiter.api.* +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension import org.koin.core.context.startKoin import org.koin.core.context.stopKoin @@ -64,35 +68,39 @@ class SessionExpirationTest : KoinTest { @DisplayName("GIVEN_401_THEN_refresh_token_ok_response_WHEN_content_requested_THE_tokens_are_refreshed_and_request_retried_with_new_tokens") @Test fun successRefreshResultsInRequestRetry() = runBlocking { - var sessionToReturnByMock: Session? = ContentData.loginSuccessResponse - mockServerScenarioSetup.setScenario( - ContentScenario.Unauthorized(false) - .then(ContentScenario.Success(true)), - false - ) - mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, false) - whenever(mockNetworkSessionLocalStorage.session).doAnswer { sessionToReturnByMock } - doAnswer { sessionToReturnByMock = it.arguments[0] as Session? } - .whenever(mockNetworkSessionLocalStorage).session = anyOrNull() + var sessionToReturnByMock: Session? = ContentData.loginSuccessResponse + mockServerScenarioSetup.setScenario( + ContentScenario.Unauthorized(false) + .then(ContentScenario.Success(true)), + false + ) + mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, false) + whenever(mockNetworkSessionLocalStorage.session).doAnswer { sessionToReturnByMock } + doAnswer { sessionToReturnByMock = it.arguments[0] as Session? } + .whenever(mockNetworkSessionLocalStorage).session = anyOrNull() - sut.get() + sut.get() - mockServerScenarioSetup.takeRequest() - val refreshRequest = mockServerScenarioSetup.takeRequest() - val retryAfterTokenRefreshRequest = mockServerScenarioSetup.takeRequest() + mockServerScenarioSetup.takeRequest() + val refreshRequest = mockServerScenarioSetup.takeRequest() + val retryAfterTokenRefreshRequest = mockServerScenarioSetup.takeRequest() - Assertions.assertEquals("PUT", refreshRequest.method) - Assertions.assertEquals("/login/${ContentData.loginSuccessResponse.refreshToken}", refreshRequest.path) - Assertions.assertEquals(null, refreshRequest.getHeader("Authorization")) - Assertions.assertEquals("Android", refreshRequest.getHeader("Platform")) - Assertions.assertEquals("", refreshRequest.body.readUtf8()) - Assertions.assertEquals( - ContentData.refreshSuccessResponse.accessToken, - retryAfterTokenRefreshRequest.getHeader("Authorization") - ) - verify(mockNetworkSessionLocalStorage, times(1)).session = ContentData.refreshSuccessResponse - verifyZeroInteractions(mockNetworkSessionExpirationListener) - } + Assertions.assertEquals("PUT", refreshRequest.method) + Assertions.assertEquals( + "/login/${ContentData.loginSuccessResponse.refreshToken}", + refreshRequest.path + ) + Assertions.assertEquals(null, refreshRequest.getHeader("Authorization")) + Assertions.assertEquals("Android", refreshRequest.getHeader("Platform")) + Assertions.assertEquals("", refreshRequest.body.readUtf8()) + Assertions.assertEquals( + ContentData.refreshSuccessResponse.accessToken, + retryAfterTokenRefreshRequest.getHeader("Authorization") + ) + verify(mockNetworkSessionLocalStorage, times(1)).session = + ContentData.refreshSuccessResponse + verifyZeroInteractions(mockNetworkSessionExpirationListener) + } @DisplayName("GIVEN 401 THEN failing refresh WHEN content requested THE error is returned and callback is Called") @Test From 1aa0b48b0a34a6bdb324e16951c3d37bc4a22901 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Sat, 18 Sep 2021 17:07:41 +0300 Subject: [PATCH 3/3] Fix codeAnalysis errors --- .github/workflows/pull-request-jobs.yml | 16 ++++++++++------ app/build.gradle | 1 + .../test/showcase/ui/home/MainActivity.kt | 4 +++- .../core/content/GetAllContentUseCase.kt | 19 +++++++++++++++---- .../test/showcase/core/shared/AnswerUtils.kt | 1 + detekt/detekt.yml | 2 +- .../network/session/SessionAuthenticator.kt | 6 +++++- .../network/shared/ExceptionWrapper.kt | 1 + 8 files changed, 37 insertions(+), 13 deletions(-) diff --git a/.github/workflows/pull-request-jobs.yml b/.github/workflows/pull-request-jobs.yml index 7aa4d80..2b4d0af 100644 --- a/.github/workflows/pull-request-jobs.yml +++ b/.github/workflows/pull-request-jobs.yml @@ -27,15 +27,17 @@ jobs: - name: Run detekt run: ./gradlew detekt - name: Upload Detekt Results - - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v2 + if: always() with: name: Detekt Results - path: build/reports/detekt/detekt.html + path: build/reports/detekt.html retention-days: 1 - name: Run ktlint run: ./gradlew ktlintCheck - name: Upload ktLint Results - - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v2 + if: always() with: name: ktLint Results path: ./**/build/reports/ktlint/**/*ktlint*Check.txt @@ -43,7 +45,8 @@ jobs: - name: Run Lint run: ./gradlew lint - name: Upload Lint Results - - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v2 + if: always() with: name: Lint Results path: ./**/build/reports/*lint-results*.html @@ -62,9 +65,10 @@ jobs: distribution: 'adopt' java-version: '11' - name: Run Unit Tests - run: ./gradlew testReleaseUnit + run: ./gradlew unitTests - name: Upload Test Results - - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v2 + if: always() with: name: Unit Test Results path: ./**/build/reports/tests/**/index.html diff --git a/app/build.gradle b/app/build.gradle index b227cb4..a390928 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,6 +10,7 @@ android { defaultConfig { applicationId "org.fnives.test.showcase" minSdkVersion 21 + //noinspection OldTargetApi // todo targetSdkVersion 30 versionCode 1 versionName "1.0" diff --git a/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt b/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt index aef19a9..187304a 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/home/MainActivity.kt @@ -32,7 +32,9 @@ class MainActivity : AppCompatActivity() { val adapter = FavouriteContentAdapter(viewModel.mapToAdapterListener()) binding.recycler.layoutManager = LinearLayoutManager(this) - binding.recycler.addItemDecoration(VerticalSpaceItemDecoration(resources.getDimensionPixelOffset(R.dimen.padding))) + binding.recycler.addItemDecoration( + VerticalSpaceItemDecoration(resources.getDimensionPixelOffset(R.dimen.padding)) + ) binding.recycler.adapter = adapter viewModel.content.observe(this) { diff --git a/core/src/main/java/org/fnives/test/showcase/core/content/GetAllContentUseCase.kt b/core/src/main/java/org/fnives/test/showcase/core/content/GetAllContentUseCase.kt index d078d68..24a4b63 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/content/GetAllContentUseCase.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/content/GetAllContentUseCase.kt @@ -14,7 +14,10 @@ class GetAllContentUseCase internal constructor( ) { fun get(): Flow>> = - contentRepository.contents.combine(favouriteContentLocalStorage.observeFavourites(), ::combineContentWithFavourites) + contentRepository.contents.combine( + favouriteContentLocalStorage.observeFavourites(), + ::combineContentWithFavourites + ) companion object { private fun combineContentWithFavourites( @@ -24,10 +27,18 @@ class GetAllContentUseCase internal constructor( when (contentResource) { is Resource.Error -> Resource.Error(contentResource.error) is Resource.Loading -> Resource.Loading() - is Resource.Success -> Resource.Success(combineContentWithFavourites(contentResource.data, favouriteContents)) + is Resource.Success -> + Resource.Success( + combineContentWithFavourites(contentResource.data, favouriteContents) + ) } - private fun combineContentWithFavourites(content: List, favourite: List): List = - content.map { FavouriteContent(content = it, isFavourite = favourite.contains(it.id)) } + private fun combineContentWithFavourites( + content: List, + favourite: List + ): List = + content.map { + FavouriteContent(content = it, isFavourite = favourite.contains(it.id)) + } } } diff --git a/core/src/main/java/org/fnives/test/showcase/core/shared/AnswerUtils.kt b/core/src/main/java/org/fnives/test/showcase/core/shared/AnswerUtils.kt index cf7bad0..90d406e 100644 --- a/core/src/main/java/org/fnives/test/showcase/core/shared/AnswerUtils.kt +++ b/core/src/main/java/org/fnives/test/showcase/core/shared/AnswerUtils.kt @@ -6,6 +6,7 @@ import org.fnives.test.showcase.model.shared.Resource import org.fnives.test.showcase.network.shared.exceptions.NetworkException import org.fnives.test.showcase.network.shared.exceptions.ParsingException +@Suppress("RethrowCaughtException") internal suspend fun wrapIntoAnswer(callback: suspend () -> T): Answer = try { Answer.Success(callback()) diff --git a/detekt/detekt.yml b/detekt/detekt.yml index 0fd7e71..6e6c0df 100644 --- a/detekt/detekt.yml +++ b/detekt/detekt.yml @@ -714,7 +714,7 @@ style: active: false ReturnCount: active: true - max: 2 + max: 5 excludedFunctions: 'equals' excludeLabeled: false excludeReturnFromLambda: true diff --git a/network/src/main/java/org/fnives/test/showcase/network/session/SessionAuthenticator.kt b/network/src/main/java/org/fnives/test/showcase/network/session/SessionAuthenticator.kt index a5a2cf2..df1c411 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/session/SessionAuthenticator.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/session/SessionAuthenticator.kt @@ -14,11 +14,15 @@ internal class SessionAuthenticator( private val networkSessionExpirationListener: NetworkSessionExpirationListener ) : Authenticator { + @Suppress("SwallowedException") override fun authenticate(route: Route?, response: Response): Request? { if (authenticationHeaderUtils.hasToken(response.request)) { return runBlocking { try { - val newSession = loginRemoteSource.refresh(networkSessionLocalStorage.session?.refreshToken.orEmpty()) + val refreshToken = networkSessionLocalStorage.session + ?.refreshToken + .orEmpty() + val newSession = loginRemoteSource.refresh(refreshToken) networkSessionLocalStorage.session = newSession return@runBlocking authenticationHeaderUtils.attachToken(response.request) } catch (throwable: Throwable) { diff --git a/network/src/main/java/org/fnives/test/showcase/network/shared/ExceptionWrapper.kt b/network/src/main/java/org/fnives/test/showcase/network/shared/ExceptionWrapper.kt index fd9cf74..8e60f8b 100644 --- a/network/src/main/java/org/fnives/test/showcase/network/shared/ExceptionWrapper.kt +++ b/network/src/main/java/org/fnives/test/showcase/network/shared/ExceptionWrapper.kt @@ -8,6 +8,7 @@ import java.io.EOFException internal object ExceptionWrapper { + @Suppress("RethrowCaughtException") @Throws(NetworkException::class, ParsingException::class) suspend fun wrap(request: suspend () -> T) = try { request()