Add pipeline: codeAnalysis, tests and clean up gradle

This commit is contained in:
Gergely Hegedus 2021-09-18 16:56:18 +03:00
parent 472b7591f5
commit 516b097e9e
27 changed files with 681 additions and 394 deletions

10
.github/dependabot.yml vendored Normal file
View file

@ -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

71
.github/workflows/pull-request-jobs.yml vendored Normal file
View file

@ -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

View file

@ -6,7 +6,6 @@ plugins {
android { android {
compileSdkVersion 30 compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig { defaultConfig {
applicationId "org.fnives.test.showcase" applicationId "org.fnives.test.showcase"
@ -25,16 +24,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
} }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures { buildFeatures {
viewBinding true viewBinding true
} }
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets { sourceSets {
androidTest { 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 // needed for androidTest
packagingOptions { packagingOptions {
exclude 'META-INF/LGPL2.1' exclude 'META-INF/LGPL2.1'
@ -66,14 +46,6 @@ android {
exclude 'META-INF/LICENSE.md' exclude 'META-INF/LICENSE.md'
exclude 'META-INF/LICENSE-notice.md' exclude 'META-INF/LICENSE-notice.md'
} }
lintOptions {
warningsAsErrors true
abortOnError true
textReport true
ignore 'Overdraw'
textOutput "stdout"
}
} }
afterEvaluate { afterEvaluate {

View file

@ -14,7 +14,8 @@
android:name=".TestShowcaseApplication" android:name=".TestShowcaseApplication"
android:theme="@style/Theme.TestShowCase" android:theme="@style/Theme.TestShowCase"
tools:ignore="AllowBackup"> tools:ignore="AllowBackup">
<activity android:name=".ui.splash.SplashActivity"> <activity android:name=".ui.splash.SplashActivity"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View file

@ -3,14 +3,11 @@ package org.fnives.test.showcase.favourite
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineDispatcher 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.core.storage.content.FavouriteContentLocalStorage
import org.fnives.test.showcase.model.content.ContentId import org.fnives.test.showcase.model.content.ContentId
import org.fnives.test.showcase.storage.database.DatabaseInitialization import org.fnives.test.showcase.storage.database.DatabaseInitialization
@ -44,25 +41,26 @@ internal class FavouriteContentLocalStorageImplTest : KoinTest {
@Test @Test
fun GIVEN_content_id_WHEN_added_to_Favourite_THEN_it_can_be_read_out() = runBlocking { 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")) 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<ContentId>()
sut.markAsFavourite(ContentId("b"))
sut.deleteAsFavourite(ContentId("b"))
val actual = sut.observeFavourites().first() val actual = sut.observeFavourites().first()
Assert.assertEquals(expected, actual) 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<ContentId>()
sut.markAsFavourite(ContentId("b"))
sut.deleteAsFavourite(ContentId("b"))
val actual = sut.observeFavourites().first()
Assert.assertEquals(expected, actual)
}
@Test @Test
fun GIVEN_empty_database_WHILE_observing_content_WHEN_favourite_added_THEN_change_is_emitted() = fun GIVEN_empty_database_WHILE_observing_content_WHEN_favourite_added_THEN_change_is_emitted() =
runBlocking<Unit> { runBlocking<Unit> {

View file

@ -27,24 +27,20 @@ class CodeKataAuthViewModel {
@DisplayName("GIVEN_initialized_viewModel_WHEN_observed_THEN_loading_false_other_fields_are_empty") @DisplayName("GIVEN_initialized_viewModel_WHEN_observed_THEN_loading_false_other_fields_are_empty")
@Test @Test
fun initialSetup() { fun initialSetup() {
} }
@DisplayName("GIVEN_password_text_WHEN_onPasswordChanged_is_called_THEN_password_livedata_is_updated") @DisplayName("GIVEN_password_text_WHEN_onPasswordChanged_is_called_THEN_password_livedata_is_updated")
@Test @Test
fun whenPasswordChangedLiveDataIsUpdated() { fun whenPasswordChangedLiveDataIsUpdated() {
} }
@DisplayName("GIVEN_username_text_WHEN_onUsernameChanged_is_called_THEN_username_livedata_is_updated") @DisplayName("GIVEN_username_text_WHEN_onUsernameChanged_is_called_THEN_username_livedata_is_updated")
@Test @Test
fun whenUsernameChangedLiveDataIsUpdated() { fun whenUsernameChangedLiveDataIsUpdated() {
} }
@DisplayName("GIVEN_no_password_or_username_WHEN_login_is_Called_THEN_empty_credentials_are_used_in_usecase") @DisplayName("GIVEN_no_password_or_username_WHEN_login_is_Called_THEN_empty_credentials_are_used_in_usecase")
@Test @Test
fun noPasswordUsesEmptyStringInLoginUseCase() { fun noPasswordUsesEmptyStringInLoginUseCase() {
} }
} }

View file

@ -1,41 +1,29 @@
package org.fnives.test.showcase.ui.splash 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.BeforeEach
import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test 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") @Disabled("CodeKata")
internal class CodeKataSplashViewModelTest { internal class CodeKataSplashViewModelTest {
@BeforeEach @BeforeEach
fun setUp() { fun setUp() {
} }
@DisplayName("GIVEN not logged in user WHEN splash started THEN after half a second navigated to authentication") @DisplayName("GIVEN not logged in user WHEN splash started THEN after half a second navigated to authentication")
@Test @Test
fun loggedOutUserGoesToAuthentication() { fun loggedOutUserGoesToAuthentication() {
} }
@DisplayName("GIVEN logged in user WHEN splash started THEN after half a second navigated to home") @DisplayName("GIVEN logged in user WHEN splash started THEN after half a second navigated to home")
@Test @Test
fun loggedInUserGoestoHome() { fun loggedInUserGoestoHome() {
} }
@DisplayName("GIVEN not logged in user WHEN splash started THEN before half a second no event is sent") @DisplayName("GIVEN not logged in user WHEN splash started THEN before half a second no event is sent")
@Test @Test
fun withoutEnoughTimeNoNavigationHappens() { fun withoutEnoughTimeNoNavigationHappens() {
} }
} }

View file

@ -1,49 +1,22 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = "1.5.30" ext.kotlin_version = "1.5.30"
ext.detekt_version = "1.16.0" ext.detekt_version = "1.18.1"
repositories { repositories {
mavenCentral() mavenCentral()
google() google()
maven { url "https://plugins.gradle.org/m2/" } maven { url "https://plugins.gradle.org/m2/" }
} }
dependencies { 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.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jlleitschuh.gradle:ktlint-gradle:10.0.0" classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.0"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
} }
} }
//apply plugin: "io.gitlab.arturbosch.detekt" version "$detekt_version"
plugins { plugins {
id "io.gitlab.arturbosch.detekt" version "$detekt_version" 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 { allprojects {
repositories { repositories {
@ -52,10 +25,6 @@ allprojects {
} }
} }
subprojects {
apply plugin: "org.jlleitschuh.gradle.ktlint"
}
task clean(type: Delete) { task clean(type: Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }
@ -70,4 +39,8 @@ task androidTests(dependsOn: "app:connectedAndroidTest"){
description = 'Run all Android tests' description = 'Run all Android tests'
} }
apply from: 'gradlescripts/versions.gradle' 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'

View file

@ -8,15 +8,6 @@ java {
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
} }
test {
useJUnitPlatform()
testLogging {
events 'started', 'passed', 'skipped', 'failed'
exceptionFormat "full"
showStandardStreams true
}
}
compileKotlin { compileKotlin {
kotlinOptions { kotlinOptions {
freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"

View file

@ -1,75 +1,50 @@
package org.fnives.test.showcase.core.content 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 kotlinx.coroutines.test.runBlockingTest
import org.fnives.test.showcase.core.shared.UnexpectedException import org.junit.jupiter.api.BeforeEach
import org.fnives.test.showcase.model.content.Content import org.junit.jupiter.api.Disabled
import org.fnives.test.showcase.model.content.ContentId import org.junit.jupiter.api.DisplayName
import org.fnives.test.showcase.model.content.ImageUrl import org.junit.jupiter.api.Test
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
@Disabled("CodeKata") @Disabled("CodeKata")
class CodeKataContentRepositoryTest { class CodeKataContentRepositoryTest {
@BeforeEach @BeforeEach
fun setUp() { fun setUp() {
} }
@DisplayName("GIVEN no interaction THEN remote source is not called") @DisplayName("GIVEN no interaction THEN remote source is not called")
@Test @Test
fun fetchingIsLazy() { fun fetchingIsLazy() {
} }
@DisplayName("GIVEN content response WHEN content observed THEN loading AND data is returned") @DisplayName("GIVEN content response WHEN content observed THEN loading AND data is returned")
@Test @Test
fun happyFlow() = runBlockingTest { fun happyFlow() = runBlockingTest {
} }
@DisplayName("GIVEN content error WHEN content observed THEN loading AND data is returned") @DisplayName("GIVEN content error WHEN content observed THEN loading AND data is returned")
@Test @Test
fun errorFlow() = runBlockingTest { fun errorFlow() = runBlockingTest {
} }
@DisplayName("GIVEN saved cache WHEN collected THEN cache is returned") @DisplayName("GIVEN saved cache WHEN collected THEN cache is returned")
@Test @Test
fun verifyCaching() = runBlockingTest { fun verifyCaching() = runBlockingTest {
} }
@DisplayName("GIVEN no response from remote source WHEN content observed THEN loading is returned") @DisplayName("GIVEN no response from remote source WHEN content observed THEN loading is returned")
@Test @Test
fun loadingIsShownBeforeTheRequestIsReturned() = runBlockingTest { fun loadingIsShownBeforeTheRequestIsReturned() = runBlockingTest {
} }
@DisplayName("GIVEN content response THEN error WHEN fetched THEN returned states are loading data loading error") @DisplayName("GIVEN content response THEN error WHEN fetched THEN returned states are loading data loading error")
@Test @Test
fun whenFetchingRequestIsCalledAgain() = runBlockingTest { fun whenFetchingRequestIsCalledAgain() = runBlockingTest {
} }
@DisplayName("GIVEN content response THEN error WHEN fetched THEN only 4 items are emitted") @DisplayName("GIVEN content response THEN error WHEN fetched THEN only 4 items are emitted")
@Test @Test
fun noAdditionalItemsEmitted() { fun noAdditionalItemsEmitted() {
} }
} }

View file

@ -16,7 +16,15 @@ import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test 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") @Suppress("TestFunctionName")
internal class ContentRepositoryTest { internal class ContentRepositoryTest {
@ -43,10 +51,19 @@ internal class ContentRepositoryTest {
@Test @Test
fun happyFlow() = runBlockingTest { fun happyFlow() = runBlockingTest {
val expected = listOf( val expected = listOf(
Resource.Loading(), Resource.Loading(),
Resource.Success(listOf(Content(ContentId("a"), "", "", ImageUrl("")))) 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() val actual = sut.contents.take(2).toList()
@ -58,8 +75,8 @@ internal class ContentRepositoryTest {
fun errorFlow() = runBlockingTest { fun errorFlow() = runBlockingTest {
val exception = RuntimeException() val exception = RuntimeException()
val expected = listOf( val expected = listOf(
Resource.Loading(), Resource.Loading(),
Resource.Error<List<Content>>(UnexpectedException(exception)) Resource.Error<List<Content>>(UnexpectedException(exception))
) )
whenever(mockContentRemoteSource.get()).doThrow(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") @DisplayName("GIVEN content response THEN error WHEN fetched THEN returned states are loading data loading error")
@Test @Test
fun whenFetchingRequestIsCalledAgain() = fun whenFetchingRequestIsCalledAgain() =
runBlockingTest(testDispatcher) { runBlockingTest(testDispatcher) {
val exception = RuntimeException() val exception = RuntimeException()
val expected = listOf( val expected = listOf(
Resource.Loading(), Resource.Loading(),
Resource.Success(emptyList()), Resource.Success(emptyList()),
Resource.Loading(), Resource.Loading(),
Resource.Error<List<Content>>(UnexpectedException(exception)) Resource.Error<List<Content>>(UnexpectedException(exception))
) )
var first = true var first = true
whenever(mockContentRemoteSource.get()).doAnswer { whenever(mockContentRemoteSource.get()).doAnswer {
if (first) emptyList<Content>().also { first = false } else throw exception if (first) emptyList<Content>().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())
} }
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") @DisplayName("GIVEN content response THEN error WHEN fetched THEN only 4 items are emitted")
@Test @Test
fun noAdditionalItemsEmitted() { fun noAdditionalItemsEmitted() {
@ -129,10 +146,10 @@ internal class ContentRepositoryTest {
runBlockingTest(testDispatcher) { runBlockingTest(testDispatcher) {
val exception = RuntimeException() val exception = RuntimeException()
val expected = listOf( val expected = listOf(
Resource.Loading(), Resource.Loading(),
Resource.Success(emptyList()), Resource.Success(emptyList()),
Resource.Loading(), Resource.Loading(),
Resource.Error<List<Content>>(UnexpectedException(exception)) Resource.Error<List<Content>>(UnexpectedException(exception))
) )
var first = true var first = true
whenever(mockContentRemoteSource.get()).doAnswer { whenever(mockContentRemoteSource.get()).doAnswer {

View file

@ -1,16 +1,9 @@
package org.fnives.test.showcase.core.login package org.fnives.test.showcase.core.login
import kotlinx.coroutines.test.runBlockingTest import org.junit.jupiter.api.BeforeEach
import org.fnives.test.showcase.core.shared.UnexpectedException import org.junit.jupiter.api.Disabled
import org.fnives.test.showcase.core.storage.UserDataLocalStorage import org.junit.jupiter.api.DisplayName
import org.fnives.test.showcase.model.auth.LoginCredentials import org.junit.jupiter.api.Test
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.*
@Disabled("CodeKata") @Disabled("CodeKata")
class CodeKataSecondLoginUseCaseTest { class CodeKataSecondLoginUseCaseTest {
@ -49,4 +42,4 @@ class CodeKataSecondLoginUseCaseTest {
fun invalidResponseResultsInErrorReturned() { fun invalidResponseResultsInErrorReturned() {
TODO() TODO()
} }
} }

View file

@ -13,8 +13,14 @@ import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.mockito.kotlin.* import org.mockito.kotlin.doReturn
import java.util.stream.Stream 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") @Suppress("TestFunctionName")
internal class LoginUseCaseTest { internal class LoginUseCaseTest {

View file

@ -1,12 +1,6 @@
package org.fnives.test.showcase.core.session package org.fnives.test.showcase.core.session
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.kotlin.*
@Disabled("CodeKata") @Disabled("CodeKata")
class CodeKataFirstSessionExpirationAdapterTest { class CodeKataFirstSessionExpirationAdapterTest
}

View file

@ -1,56 +1,79 @@
build: build:
maxIssues: 15 maxIssues: 0
excludeCorrectable: false
weights: weights:
# complexity: 2 # complexity: 2
# LongParameterList: 1 # LongParameterList: 1
# style: 1 # style: 1
# comments: 1 # comments: 1
config: config:
validation: true validation: true
# when writing own rules with new properties, exclude the property path e.g.: "my_rule_set,.*>.*>[my_property]" warningsAsErrors: false
excludes: "" # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
excludes: ''
processors: processors:
active: true active: true
exclude: exclude:
# - 'DetektProgressListener' - 'DetektProgressListener'
# - 'KtFileCountProcessor'
# - 'PackageCountProcessor'
# - 'ClassCountProcessor'
# - 'FunctionCountProcessor' # - 'FunctionCountProcessor'
# - 'PropertyCountProcessor' # - 'PropertyCountProcessor'
# - 'ClassCountProcessor' # - 'ProjectComplexityProcessor'
# - 'PackageCountProcessor' # - 'ProjectCognitiveComplexityProcessor'
# - 'KtFileCountProcessor' # - 'ProjectLLOCProcessor'
# - 'ProjectCLOCProcessor'
# - 'ProjectLOCProcessor'
# - 'ProjectSLOCProcessor'
# - 'LicenseHeaderLoaderExtension'
console-reports: console-reports:
active: true active: true
exclude: exclude:
# - 'ProjectStatisticsReport' - 'ProjectStatisticsReport'
# - 'ComplexityReport' - 'ComplexityReport'
# - 'NotificationReport' - 'NotificationReport'
# - 'FindingsReport' # - 'FindingsReport'
- 'FileBasedFindingsReport' - 'FileBasedFindingsReport'
# - 'BuildFailureReport'
output-reports:
active: true
exclude:
# - 'TxtOutputReport'
# - 'XmlOutputReport'
# - 'HtmlOutputReport'
comments: comments:
active: true active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" AbsentOrWrongFileLicense:
active: false
licenseTemplateFile: 'license.template'
licenseTemplateIsRegex: false
CommentOverPrivateFunction: CommentOverPrivateFunction:
active: false active: false
CommentOverPrivateProperty: CommentOverPrivateProperty:
active: false active: false
DeprecatedBlockTag:
active: false
EndOfSentenceFormat: EndOfSentenceFormat:
active: false active: false
endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!:]$) endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
UndocumentedPublicClass: UndocumentedPublicClass:
active: false active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
searchInNestedClass: true searchInNestedClass: true
searchInInnerClass: true searchInInnerClass: true
searchInInnerObject: true searchInInnerObject: true
searchInInnerInterface: true searchInInnerInterface: true
UndocumentedPublicFunction: UndocumentedPublicFunction:
active: false active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
UndocumentedPublicProperty: UndocumentedPublicProperty:
active: false active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
complexity: complexity:
active: true active: true
@ -61,57 +84,86 @@ complexity:
active: false active: false
threshold: 10 threshold: 10
includeStaticDeclarations: false includeStaticDeclarations: false
includePrivateDeclarations: false
ComplexMethod: ComplexMethod:
active: true active: true
threshold: 25 threshold: 15
ignoreSingleWhenExpression: false ignoreSingleWhenExpression: false
ignoreSimpleWhenEntries: false ignoreSimpleWhenEntries: false
ignoreNestingFunctions: false ignoreNestingFunctions: false
nestingFunctions: run,let,apply,with,also,use,forEach,isNotNull,ifNull nestingFunctions:
- 'also'
- 'apply'
- 'forEach'
- 'isNotNull'
- 'ifNull'
- 'let'
- 'run'
- 'use'
- 'with'
LabeledExpression: LabeledExpression:
active: false active: false
ignoredLabels: "" ignoredLabels: []
LargeClass: LargeClass:
active: true active: true
threshold: 600 threshold: 600
LongMethod: LongMethod:
active: true active: true
threshold: 200 threshold: 60
ignoreAnnotated: []
LongParameterList: LongParameterList:
active: true active: true
functionThreshold: 6 functionThreshold: 6
constructorThreshold: 6 constructorThreshold: 7
ignoreDefaultParameters: false ignoreDefaultParameters: false
ignoreDataClasses: true
ignoreAnnotated: []
MethodOverloading: MethodOverloading:
active: false active: false
threshold: 6 threshold: 6
NamedArguments:
active: false
threshold: 3
NestedBlockDepth: NestedBlockDepth:
active: true active: true
threshold: 100 threshold: 4
ReplaceSafeCallChainWithRun:
active: false
StringLiteralDuplication: StringLiteralDuplication:
active: false active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
threshold: 3 threshold: 3
ignoreAnnotation: true ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^' ignoreStringsRegex: '$^'
TooManyFunctions: TooManyFunctions:
active: true active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
thresholdInFiles: 25 thresholdInFiles: 11
thresholdInClasses: 25 thresholdInClasses: 11
thresholdInInterfaces: 25 thresholdInInterfaces: 11
thresholdInObjects: 25 thresholdInObjects: 11
thresholdInEnums: 25 thresholdInEnums: 11
ignoreDeprecated: false ignoreDeprecated: false
ignorePrivate: false ignorePrivate: false
ignoreOverridden: false ignoreOverridden: false
coroutines:
active: true
GlobalCoroutineUsage:
active: false
RedundantSuspendModifier:
active: false
SleepInsteadOfDelay:
active: false
SuspendFunWithFlowReturnType:
active: false
empty-blocks: empty-blocks:
active: true active: true
EmptyCatchBlock: EmptyCatchBlock:
active: true active: true
allowedExceptionNameRegex: "^(_|(ignore|expected).*)" allowedExceptionNameRegex: '_|(ignore|expected).*'
EmptyClassBlock: EmptyClassBlock:
active: true active: true
EmptyDefaultConstructor: EmptyDefaultConstructor:
@ -135,6 +187,8 @@ empty-blocks:
active: true active: true
EmptySecondaryConstructor: EmptySecondaryConstructor:
active: true active: true
EmptyTryBlock:
active: true
EmptyWhenBlock: EmptyWhenBlock:
active: true active: true
EmptyWhileBlock: EmptyWhileBlock:
@ -143,53 +197,73 @@ empty-blocks:
exceptions: exceptions:
active: true active: true
ExceptionRaisedInUnexpectedLocation: ExceptionRaisedInUnexpectedLocation:
active: false active: true
methodNames: 'toString,hashCode,equals,finalize' methodNames:
- 'equals'
- 'finalize'
- 'hashCode'
- 'toString'
InstanceOfCheckForException: InstanceOfCheckForException:
active: false active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
NotImplementedDeclaration: NotImplementedDeclaration:
active: false active: false
ObjectExtendsThrowable:
active: false
PrintStackTrace: PrintStackTrace:
active: false active: true
RethrowCaughtException: RethrowCaughtException:
active: false active: true
ReturnFromFinally: ReturnFromFinally:
active: false active: true
ignoreLabeled: false ignoreLabeled: false
SwallowedException: SwallowedException:
active: false active: true
ignoredExceptionTypes: 'InterruptedException,NumberFormatException,ParseException,MalformedURLException' ignoredExceptionTypes:
allowedExceptionNameRegex: "^(_|(ignore|expected).*)" - 'InterruptedException'
- 'MalformedURLException'
- 'NumberFormatException'
- 'ParseException'
allowedExceptionNameRegex: '_|(ignore|expected).*'
ThrowingExceptionFromFinally: ThrowingExceptionFromFinally:
active: false active: true
ThrowingExceptionInMain: ThrowingExceptionInMain:
active: false active: false
ThrowingExceptionsWithoutMessageOrCause: ThrowingExceptionsWithoutMessageOrCause:
active: true active: true
exceptions: 'IllegalArgumentException,IllegalStateException,IOException' excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
exceptions:
- 'ArrayIndexOutOfBoundsException'
- 'Exception'
- 'IllegalArgumentException'
- 'IllegalMonitorStateException'
- 'IllegalStateException'
- 'IndexOutOfBoundsException'
- 'NullPointerException'
- 'RuntimeException'
- 'Throwable'
ThrowingNewInstanceOfSameException: ThrowingNewInstanceOfSameException:
active: true active: true
TooGenericExceptionCaught: TooGenericExceptionCaught:
active: true active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
exceptionNames: exceptionNames:
- ArrayIndexOutOfBoundsException - 'ArrayIndexOutOfBoundsException'
- Error - 'Error'
- Exception - 'Exception'
- IllegalMonitorStateException - 'IllegalMonitorStateException'
- NullPointerException - 'IndexOutOfBoundsException'
- IndexOutOfBoundsException - 'NullPointerException'
- RuntimeException - 'RuntimeException'
- Throwable - 'Throwable'
allowedExceptionNameRegex: "^(_|(ignore|expected).*)" allowedExceptionNameRegex: '_|(ignore|expected).*'
TooGenericExceptionThrown: TooGenericExceptionThrown:
active: true active: true
exceptionNames: exceptionNames:
- Error - 'Error'
- Exception - 'Exception'
- Throwable - 'RuntimeException'
- RuntimeException - 'Throwable'
formatting: formatting:
active: true active: true
@ -198,28 +272,42 @@ formatting:
AnnotationOnSeparateLine: AnnotationOnSeparateLine:
active: false active: false
autoCorrect: true autoCorrect: true
AnnotationSpacing:
active: false
autoCorrect: true
ArgumentListWrapping:
active: false
autoCorrect: true
indentSize: 4
maxLineLength: 120
ChainWrapping: ChainWrapping:
active: true active: true
autoCorrect: true autoCorrect: true
CommentSpacing: CommentSpacing:
active: true active: true
autoCorrect: true autoCorrect: true
EnumEntryNameCase:
active: false
autoCorrect: true
Filename: Filename:
active: true active: true
FinalNewline: FinalNewline:
active: true active: true
autoCorrect: true autoCorrect: true
insertFinalNewLine: true
ImportOrdering: ImportOrdering:
active: true active: false
autoCorrect: true autoCorrect: true
layout: '*,java.**,javax.**,kotlin.**,^'
Indentation: Indentation:
active: true active: false
autoCorrect: true autoCorrect: true
indentSize: 4 indentSize: 4
continuationIndentSize: 4 continuationIndentSize: 4
MaximumLineLength: MaximumLineLength:
active: true active: true
maxLineLength: 250 maxLineLength: 120
ignoreBackTickedIdentifier: false
ModifierOrdering: ModifierOrdering:
active: true active: true
autoCorrect: true autoCorrect: true
@ -235,6 +323,9 @@ formatting:
NoEmptyClassBody: NoEmptyClassBody:
active: true active: true
autoCorrect: true autoCorrect: true
NoEmptyFirstLineInMethodBlock:
active: false
autoCorrect: true
NoLineBreakAfterElse: NoLineBreakAfterElse:
active: true active: true
autoCorrect: true autoCorrect: true
@ -265,6 +356,10 @@ formatting:
active: true active: true
autoCorrect: true autoCorrect: true
indentSize: 4 indentSize: 4
maxLineLength: 120
SpacingAroundAngleBrackets:
active: false
autoCorrect: true
SpacingAroundColon: SpacingAroundColon:
active: true active: true
autoCorrect: true autoCorrect: true
@ -277,6 +372,9 @@ formatting:
SpacingAroundDot: SpacingAroundDot:
active: true active: true
autoCorrect: true autoCorrect: true
SpacingAroundDoubleColon:
active: false
autoCorrect: true
SpacingAroundKeyword: SpacingAroundKeyword:
active: true active: true
autoCorrect: true autoCorrect: true
@ -289,85 +387,108 @@ formatting:
SpacingAroundRangeOperator: SpacingAroundRangeOperator:
active: true active: true
autoCorrect: true autoCorrect: true
SpacingAroundUnaryOperator:
active: false
autoCorrect: true
SpacingBetweenDeclarationsWithAnnotations:
active: false
autoCorrect: true
SpacingBetweenDeclarationsWithComments:
active: false
autoCorrect: true
StringTemplate: StringTemplate:
active: true active: true
autoCorrect: true autoCorrect: true
naming: naming:
active: true active: true
BooleanPropertyNaming:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
allowedPattern: '^(is|has|are)'
ClassNaming: ClassNaming:
active: true active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
classPattern: '[A-Z$][a-zA-Z0-9$]*' classPattern: '[A-Z][a-zA-Z0-9]*'
ConstructorParameterNaming: ConstructorParameterNaming:
active: true 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]*' parameterPattern: '[a-z][A-Za-z0-9]*'
privateParameterPattern: '[a-z][A-Za-z0-9]*' privateParameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^' excludeClassPattern: '$^'
ignoreOverridden: true
EnumNaming: EnumNaming:
active: true active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*' enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName: ForbiddenClassName:
active: false active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
forbiddenName: '' forbiddenName: []
FunctionMaxLength: FunctionMaxLength:
active: false active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
maximumFunctionNameLength: 30 maximumFunctionNameLength: 30
FunctionMinLength: FunctionMinLength:
active: false active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
minimumFunctionNameLength: 3 minimumFunctionNameLength: 3
FunctionNaming: FunctionNaming:
active: true active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$' functionPattern: '([a-z][a-zA-Z0-9]*)|(`.*`)'
excludeClassPattern: '$^' excludeClassPattern: '$^'
ignoreOverridden: true ignoreOverridden: true
ignoreAnnotated:
- 'Composable'
FunctionParameterNaming: FunctionParameterNaming:
active: true 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]*' parameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^' excludeClassPattern: '$^'
ignoreOverridden: true ignoreOverridden: true
InvalidPackageDeclaration: InvalidPackageDeclaration:
active: false active: false
excludes: ['**/*.kts']
rootPackage: '' rootPackage: ''
MatchingDeclarationName: MatchingDeclarationName:
active: true active: true
mustBeFirst: true
MemberNameEqualsClassName: MemberNameEqualsClassName:
active: true active: true
ignoreOverridden: true ignoreOverridden: true
NoNameShadowing:
active: false
NonBooleanPropertyPrefixedWithIs:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
ObjectPropertyNaming: ObjectPropertyNaming:
active: true 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]*' constantPattern: '[A-Za-z][_A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*' propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
PackageNaming: PackageNaming:
active: false active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$' packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
TopLevelPropertyNaming: TopLevelPropertyNaming:
active: true active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
constantPattern: '[A-Z][_A-Z0-9]*' constantPattern: '[A-Z][_A-Z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*' propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
VariableMaxLength: VariableMaxLength:
active: false active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
maximumVariableNameLength: 64 maximumVariableNameLength: 64
VariableMinLength: VariableMinLength:
active: false active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
minimumVariableNameLength: 1 minimumVariableNameLength: 1
VariableNaming: VariableNaming:
active: true 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]*' variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^' excludeClassPattern: '$^'
@ -379,29 +500,52 @@ performance:
active: true active: true
ForEachOnRange: ForEachOnRange:
active: true active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
SpreadOperator: SpreadOperator:
active: true active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
UnnecessaryTemporaryInstantiation: UnnecessaryTemporaryInstantiation:
active: true active: true
potential-bugs: potential-bugs:
active: true active: true
AvoidReferentialEquality:
active: false
forbiddenTypePatterns:
- 'kotlin.String'
CastToNullableType:
active: false
Deprecation: Deprecation:
active: false active: false
DontDowncastCollectionTypes:
active: false
DoubleMutabilityForCollection:
active: false
DuplicateCaseInWhenExpression: DuplicateCaseInWhenExpression:
active: true active: true
EqualsAlwaysReturnsTrueOrFalse: EqualsAlwaysReturnsTrueOrFalse:
active: true active: true
EqualsWithHashCodeExist: EqualsWithHashCodeExist:
active: true active: true
ExitOutsideMain:
active: false
ExplicitGarbageCollectionCall: ExplicitGarbageCollectionCall:
active: true active: true
HasPlatformType: HasPlatformType:
active: false active: false
ImplicitDefaultLocale: IgnoredReturnValue:
active: false active: false
restrictToAnnotatedMethods: true
returnValueAnnotations:
- '*.CheckResult'
- '*.CheckReturnValue'
ignoreReturnValueAnnotations:
- '*.CanIgnoreReturnValue'
ImplicitDefaultLocale:
active: true
ImplicitUnitReturnType:
active: false
allowExplicitReturnType: true
InvalidRange: InvalidRange:
active: true active: true
IteratorHasNextCallsNextMethod: IteratorHasNextCallsNextMethod:
@ -410,20 +554,33 @@ potential-bugs:
active: true active: true
LateinitUsage: LateinitUsage:
active: false active: false
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
excludeAnnotatedProperties: "" excludeAnnotatedProperties: []
ignoreOnClassesPattern: "" ignoreOnClassesPattern: ''
MapGetWithNotNullAssertionOperator:
active: false
MissingWhenCase: MissingWhenCase:
active: true active: true
allowElseExpression: true
NullableToStringCall:
active: false
RedundantElseInWhen: RedundantElseInWhen:
active: true active: true
UnconditionalJumpStatementInLoop: UnconditionalJumpStatementInLoop:
active: false active: false
UnnecessaryNotNullOperator:
active: true
UnnecessarySafeCall:
active: true
UnreachableCatchBlock:
active: false
UnreachableCode: UnreachableCode:
active: true active: true
UnsafeCallOnNullableType: UnsafeCallOnNullableType:
active: true active: true
UnsafeCast: UnsafeCast:
active: true
UnusedUnaryOperator:
active: false active: false
UselessPostfixExpression: UselessPostfixExpression:
active: false active: false
@ -432,30 +589,51 @@ potential-bugs:
style: style:
active: true active: true
ClassOrdering:
active: false
CollapsibleIfStatements: CollapsibleIfStatements:
active: true active: false
DataClassContainsFunctions: DataClassContainsFunctions:
active: true active: false
conversionFunctionPrefix: 'to' conversionFunctionPrefix: 'to'
DataClassShouldBeImmutable: DataClassShouldBeImmutable:
active: false active: false
DestructuringDeclarationWithTooManyEntries:
active: false
maxDestructuringEntries: 3
EqualsNullCall: EqualsNullCall:
active: true active: true
EqualsOnSignatureLine: EqualsOnSignatureLine:
active: false active: false
ExplicitCollectionElementAccessMethod:
active: false
ExplicitItLambdaParameter: ExplicitItLambdaParameter:
active: false active: false
ExpressionBodySyntax: ExpressionBodySyntax:
active: true active: false
includeLineWrapping: false includeLineWrapping: false
ForbiddenComment: ForbiddenComment:
active: true active: true
values: 'TODO:,FIXME:,STOPSHIP:' values:
allowedPatterns: "" - 'FIXME:'
- 'STOPSHIP:'
- 'TODO:'
allowedPatterns: ''
ForbiddenImport: ForbiddenImport:
active: false active: false
imports: '' imports: []
forbiddenPatterns: "" forbiddenPatterns: ''
ForbiddenMethodCall:
active: false
methods:
- 'kotlin.io.print'
- 'kotlin.io.println'
ForbiddenPublicDataClass:
active: true
excludes: ['**']
ignorePackages:
- '*.internal'
- '*.internal.*'
ForbiddenVoid: ForbiddenVoid:
active: false active: false
ignoreOverridden: false ignoreOverridden: false
@ -463,30 +641,44 @@ style:
FunctionOnlyReturningConstant: FunctionOnlyReturningConstant:
active: true active: true
ignoreOverridableFunction: true ignoreOverridableFunction: true
excludedFunctions: 'describeContents' ignoreActualFunction: true
excludeAnnotatedFunction: "dagger.Provides" excludedFunctions: ''
excludeAnnotatedFunction:
- 'dagger.Provides'
LibraryCodeMustSpecifyReturnType: LibraryCodeMustSpecifyReturnType:
active: true active: true
excludes: ['**']
LibraryEntitiesShouldNotBePublic:
active: true
excludes: ['**']
LoopWithTooManyJumpStatements: LoopWithTooManyJumpStatements:
active: true active: true
maxJumpCount: 1 maxJumpCount: 1
MagicNumber: MagicNumber:
active: true active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
ignoreNumbers: '-1,0,1,2' ignoreNumbers:
- '-1'
- '0'
- '1'
- '2'
ignoreHashCodeFunction: true ignoreHashCodeFunction: true
ignorePropertyDeclaration: false ignorePropertyDeclaration: false
ignoreLocalVariableDeclaration: false
ignoreConstantDeclaration: true ignoreConstantDeclaration: true
ignoreCompanionObjectPropertyDeclaration: true ignoreCompanionObjectPropertyDeclaration: true
ignoreAnnotation: false ignoreAnnotation: false
ignoreNamedArgument: true ignoreNamedArgument: true
ignoreEnums: false ignoreEnums: false
ignoreRanges: false ignoreRanges: false
ignoreExtensionFunctions: true
MandatoryBracesIfStatements: MandatoryBracesIfStatements:
active: true active: false
MandatoryBracesLoops:
active: false
MaxLineLength: MaxLineLength:
active: true active: true
maxLineLength: 160 #default: 120 maxLineLength: 120
excludePackageStatements: true excludePackageStatements: true
excludeImportStatements: true excludeImportStatements: true
excludeCommentStatements: false excludeCommentStatements: false
@ -494,12 +686,16 @@ style:
active: true active: true
ModifierOrder: ModifierOrder:
active: true active: true
NestedClassesVisibility: MultilineLambdaItParameter:
active: false active: false
NestedClassesVisibility:
active: true
NewLineAtEndOfFile: NewLineAtEndOfFile:
active: true active: true
NoTabs: NoTabs:
active: false active: false
ObjectLiteralToLambda:
active: false
OptionalAbstractKeyword: OptionalAbstractKeyword:
active: true active: true
OptionalUnit: OptionalUnit:
@ -507,29 +703,32 @@ style:
OptionalWhenBraces: OptionalWhenBraces:
active: false active: false
PreferToOverPairSyntax: PreferToOverPairSyntax:
active: true active: false
ProtectedMemberInFinalClass: ProtectedMemberInFinalClass:
active: true active: true
RedundantExplicitType: RedundantExplicitType:
active: true active: false
RedundantHigherOrderMapUsage:
active: false
RedundantVisibilityModifierRule: RedundantVisibilityModifierRule:
active: false active: false
ReturnCount: ReturnCount:
active: true active: true
max: 5 max: 2
excludedFunctions: "equals" excludedFunctions: 'equals'
excludeLabeled: false excludeLabeled: false
excludeReturnFromLambda: true excludeReturnFromLambda: true
excludeGuardClauses: false excludeGuardClauses: false
SafeCast: SafeCast:
active: true active: true
SerialVersionUIDInSerializableClass: SerialVersionUIDInSerializableClass:
active: false active: true
SpacingBetweenPackageAndImports: SpacingBetweenPackageAndImports:
active: false active: false
ThrowsCount: ThrowsCount:
active: true active: true
max: 10 max: 2
excludeGuardClauses: false
TrailingWhitespace: TrailingWhitespace:
active: false active: false
UnderscoresInNumericLiterals: UnderscoresInNumericLiterals:
@ -537,43 +736,62 @@ style:
acceptableDecimalLength: 5 acceptableDecimalLength: 5
UnnecessaryAbstractClass: UnnecessaryAbstractClass:
active: true active: true
excludeAnnotatedClasses: "dagger.Module" excludeAnnotatedClasses:
- 'dagger.Module'
UnnecessaryAnnotationUseSiteTarget:
active: false
UnnecessaryApply: UnnecessaryApply:
active: true active: true
UnnecessaryFilter:
active: false
UnnecessaryInheritance: UnnecessaryInheritance:
active: true active: true
UnnecessaryLet: UnnecessaryLet:
active: true active: false
UnnecessaryParentheses: UnnecessaryParentheses:
active: true active: false
UntilInsteadOfRangeTo: UntilInsteadOfRangeTo:
active: false active: false
UnusedImports: UnusedImports:
active: true active: false
UnusedPrivateClass: UnusedPrivateClass:
active: true active: true
UnusedPrivateMember: UnusedPrivateMember:
active: true active: true
allowedNames: "(_|ignored|expected|serialVersionUID)" allowedNames: '(_|ignored|expected|serialVersionUID)'
UseArrayLiteralsInAnnotations: UseArrayLiteralsInAnnotations:
active: false active: false
UseCheckNotNull:
active: false
UseCheckOrError: UseCheckOrError:
active: false active: false
UseDataClass: UseDataClass:
active: false active: false
excludeAnnotatedClasses: "" excludeAnnotatedClasses: []
allowVars: false allowVars: false
UseEmptyCounterpart:
active: false
UseIfEmptyOrIfBlank:
active: false
UseIfInsteadOfWhen: UseIfInsteadOfWhen:
active: false active: false
UseIsNullOrEmpty:
active: false
UseOrEmpty:
active: false
UseRequire: UseRequire:
active: false active: false
UseRequireNotNull:
active: false
UselessCallOnNotNull: UselessCallOnNotNull:
active: true active: true
UtilityClassWithPublicConstructor: UtilityClassWithPublicConstructor:
active: true active: true
VarCouldBeVal: VarCouldBeVal:
active: false active: true
WildcardImport: WildcardImport:
active: true active: true
excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
excludeImports: 'java.util.*,kotlinx.android.synthetic.*' excludeImports:
- 'java.util.*'
- 'kotlinx.android.synthetic.*'

View file

@ -17,6 +17,6 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX # Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true 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 for this project: "official" or "obsolete":
kotlin.code.style=official kotlin.code.style=official

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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

View file

@ -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")
}
}
}

View file

@ -0,0 +1,3 @@
subprojects {
apply plugin: "org.jlleitschuh.gradle.ktlint"
}

34
gradlescripts/lint.gradle Normal file
View file

@ -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"
}
}
}

View file

@ -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
}
}
}
}

View file

@ -6,10 +6,4 @@ plugins {
java { java {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
} }
compileKotlin {
kotlinOptions {
freeCompilerArgs += "-Xinline-classes"
}
}

View file

@ -9,15 +9,6 @@ java {
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
} }
test {
useJUnitPlatform()
testLogging {
events 'started', 'passed', 'skipped', 'failed'
exceptionFormat "full"
showStandardStreams true
}
}
dependencies { dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"

View file

@ -1,24 +1,11 @@
package org.fnives.test.showcase.network.auth package org.fnives.test.showcase.network.auth
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.MockResponse import org.junit.jupiter.api.AfterEach
import okhttp3.mockwebserver.MockWebServer import org.junit.jupiter.api.BeforeEach
import org.fnives.test.showcase.model.auth.LoginCredentials import org.junit.jupiter.api.Disabled
import org.fnives.test.showcase.model.network.BaseUrl import org.junit.jupiter.api.DisplayName
import org.fnives.test.showcase.model.session.Session import org.junit.jupiter.api.Test
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 java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
@ -27,54 +14,46 @@ class CodeKataLoginRemoteSourceTest {
@BeforeEach @BeforeEach
fun setUp() { fun setUp() {
} }
@AfterEach @AfterEach
fun tearDown() { fun tearDown() {
} }
@DisplayName("GIVEN successful response WHEN request is fired THEN login status success is returned") @DisplayName("GIVEN successful response WHEN request is fired THEN login status success is returned")
@Test @Test
fun successResponseIsParsedProperly() = runBlocking { fun successResponseIsParsedProperly() = runBlocking {
} }
@DisplayName("GIVEN successful response WHEN request is fired THEN the request is setup properly") @DisplayName("GIVEN successful response WHEN request is fired THEN the request is setup properly")
@Test @Test
fun requestProperlySetup() = runBlocking { fun requestProperlySetup() = runBlocking {
} }
@DisplayName("GIVEN bad request response WHEN request is fired THEN login status invalid credentials is returned") @DisplayName("GIVEN bad request response WHEN request is fired THEN login status invalid credentials is returned")
@Test @Test
fun badRequestMeansInvalidCredentials() = runBlocking { fun badRequestMeansInvalidCredentials() = runBlocking {
} }
@DisplayName("GIVEN_internal_error_response_WHEN_request_is_fired_THEN_network_exception_is_thrown") @DisplayName("GIVEN_internal_error_response_WHEN_request_is_fired_THEN_network_exception_is_thrown")
@Test @Test
fun genericErrorMeansNetworkError() { fun genericErrorMeansNetworkError() {
} }
@DisplayName("GIVEN invalid json response WHEN request is fired THEN network exception is thrown") @DisplayName("GIVEN invalid json response WHEN request is fired THEN network exception is thrown")
@Test @Test
fun invalidJsonMeansParsingException() { fun invalidJsonMeansParsingException() {
} }
@DisplayName("GIVEN malformed json response WHEN request is fired THEN network exception is thrown") @DisplayName("GIVEN malformed json response WHEN request is fired THEN network exception is thrown")
@Test @Test
fun malformedJsonMeansParsingException() { fun malformedJsonMeansParsingException() {
} }
companion object { companion object {
internal fun Any.readResourceFile(filePath: String): String = try { internal fun Any.readResourceFile(filePath: String): String = try {
BufferedReader(InputStreamReader(this.javaClass.classLoader.getResourceAsStream(filePath)!!)) BufferedReader(InputStreamReader(this.javaClass.classLoader.getResourceAsStream(filePath)!!))
.readLines().joinToString("\n") .readLines().joinToString("\n")
} catch (nullPointerException: NullPointerException) { } catch (nullPointerException: NullPointerException) {
throw IllegalArgumentException("$filePath file not found!", nullPointerException) throw IllegalArgumentException("$filePath file not found!", nullPointerException)
} }
@ -87,6 +66,5 @@ class CodeKataLoginRemoteSourceTest {
} while (true) } while (true)
} }
} }
} }
} }

View file

@ -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.MockServerScenarioSetupExtensions
import org.fnives.test.showcase.network.shared.exceptions.NetworkException import org.fnives.test.showcase.network.shared.exceptions.NetworkException
import org.fnives.test.showcase.network.shared.exceptions.ParsingException 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.junit.jupiter.api.extension.RegisterExtension
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin import org.koin.core.context.stopKoin
@ -26,6 +30,7 @@ import org.skyscreamer.jsonassert.JSONCompareMode
class LoginRemoteSourceTest : KoinTest { class LoginRemoteSourceTest : KoinTest {
private val sut by inject<LoginRemoteSource>() private val sut by inject<LoginRemoteSource>()
@RegisterExtension @RegisterExtension
@JvmField @JvmField
val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions() val mockServerScenarioSetupExtensions = MockServerScenarioSetupExtensions()
@ -76,7 +81,11 @@ class LoginRemoteSourceTest : KoinTest {
Assertions.assertEquals(null, request.getHeader("Authorization")) Assertions.assertEquals(null, request.getHeader("Authorization"))
Assertions.assertEquals("/login", request.path) Assertions.assertEquals("/login", request.path)
val loginRequest = createExpectedLoginRequestJson("a", "b") 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") @DisplayName("GIVEN bad request response WHEN request is fired THEN login status invalid credentials is returned")

View file

@ -1,21 +1,21 @@
package org.fnives.test.showcase.network.content package org.fnives.test.showcase.network.content
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer
import org.fnives.test.showcase.model.network.BaseUrl 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.di.createNetworkModules
import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener import org.fnives.test.showcase.network.session.NetworkSessionExpirationListener
import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage import org.fnives.test.showcase.network.session.NetworkSessionLocalStorage
import org.fnives.test.showcase.network.shared.exceptions.NetworkException import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.* 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.startKoin
import org.koin.core.context.stopKoin import org.koin.core.context.stopKoin
import org.koin.test.KoinTest import org.koin.test.KoinTest
import org.koin.test.inject import org.koin.test.inject
import org.mockito.kotlin.* import org.mockito.kotlin.mock
@Disabled("CodeKata") @Disabled("CodeKata")
class CodeKataSessionExpirationTest : KoinTest { class CodeKataSessionExpirationTest : KoinTest {
@ -33,12 +33,12 @@ class CodeKataSessionExpirationTest : KoinTest {
mockNetworkSessionExpirationListener = mock() mockNetworkSessionExpirationListener = mock()
startKoin { startKoin {
modules( modules(
createNetworkModules( createNetworkModules(
baseUrl = BaseUrl(mockWebServer.url("mockserver/").toString()), baseUrl = BaseUrl(mockWebServer.url("mockserver/").toString()),
enableLogging = true, enableLogging = true,
networkSessionExpirationListenerProvider = { mockNetworkSessionExpirationListener }, networkSessionExpirationListenerProvider = { mockNetworkSessionExpirationListener },
networkSessionLocalStorageProvider = { mockNetworkSessionLocalStorage } networkSessionLocalStorageProvider = { mockNetworkSessionLocalStorage }
).toList() ).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") @DisplayName("GIVEN 401 THEN failing refresh WHEN content requested THE error is returned and callback is Called")
@Test @Test
fun failingRefreshResultsInSessionExpiration() = runBlocking { fun failingRefreshResultsInSessionExpiration() = runBlocking {
} }
} }

View file

@ -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.session.NetworkSessionLocalStorage
import org.fnives.test.showcase.network.shared.MockServerScenarioSetupExtensions 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.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.junit.jupiter.api.extension.RegisterExtension
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin 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") @DisplayName("GIVEN_401_THEN_refresh_token_ok_response_WHEN_content_requested_THE_tokens_are_refreshed_and_request_retried_with_new_tokens")
@Test @Test
fun successRefreshResultsInRequestRetry() = runBlocking { fun successRefreshResultsInRequestRetry() = runBlocking {
var sessionToReturnByMock: Session? = ContentData.loginSuccessResponse var sessionToReturnByMock: Session? = ContentData.loginSuccessResponse
mockServerScenarioSetup.setScenario( mockServerScenarioSetup.setScenario(
ContentScenario.Unauthorized(false) ContentScenario.Unauthorized(false)
.then(ContentScenario.Success(true)), .then(ContentScenario.Success(true)),
false false
) )
mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, false) mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, false)
whenever(mockNetworkSessionLocalStorage.session).doAnswer { sessionToReturnByMock } whenever(mockNetworkSessionLocalStorage.session).doAnswer { sessionToReturnByMock }
doAnswer { sessionToReturnByMock = it.arguments[0] as Session? } doAnswer { sessionToReturnByMock = it.arguments[0] as Session? }
.whenever(mockNetworkSessionLocalStorage).session = anyOrNull() .whenever(mockNetworkSessionLocalStorage).session = anyOrNull()
sut.get() sut.get()
mockServerScenarioSetup.takeRequest() mockServerScenarioSetup.takeRequest()
val refreshRequest = mockServerScenarioSetup.takeRequest() val refreshRequest = mockServerScenarioSetup.takeRequest()
val retryAfterTokenRefreshRequest = mockServerScenarioSetup.takeRequest() val retryAfterTokenRefreshRequest = mockServerScenarioSetup.takeRequest()
Assertions.assertEquals("PUT", refreshRequest.method) Assertions.assertEquals("PUT", refreshRequest.method)
Assertions.assertEquals("/login/${ContentData.loginSuccessResponse.refreshToken}", refreshRequest.path) Assertions.assertEquals(
Assertions.assertEquals(null, refreshRequest.getHeader("Authorization")) "/login/${ContentData.loginSuccessResponse.refreshToken}",
Assertions.assertEquals("Android", refreshRequest.getHeader("Platform")) refreshRequest.path
Assertions.assertEquals("", refreshRequest.body.readUtf8()) )
Assertions.assertEquals( Assertions.assertEquals(null, refreshRequest.getHeader("Authorization"))
ContentData.refreshSuccessResponse.accessToken, Assertions.assertEquals("Android", refreshRequest.getHeader("Platform"))
retryAfterTokenRefreshRequest.getHeader("Authorization") Assertions.assertEquals("", refreshRequest.body.readUtf8())
) Assertions.assertEquals(
verify(mockNetworkSessionLocalStorage, times(1)).session = ContentData.refreshSuccessResponse ContentData.refreshSuccessResponse.accessToken,
verifyZeroInteractions(mockNetworkSessionExpirationListener) 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") @DisplayName("GIVEN 401 THEN failing refresh WHEN content requested THE error is returned and callback is Called")
@Test @Test