feat: separate session and credential actions
This commit is contained in:
@@ -52,6 +52,7 @@ fun ABoxManagerApp(
|
||||
val actionState = sessionActionsViewModel.uiState.collectAsStateWithLifecycle().value
|
||||
val sessionState = appSessionViewModel.uiState.collectAsStateWithLifecycle().value
|
||||
val settingsState = settingsViewModel.uiState.collectAsStateWithLifecycle().value
|
||||
val actionError = actionState.errorMessage?.asString()
|
||||
val screenKey = sessionState.screenKey()
|
||||
LaunchedEffect(screenKey) {
|
||||
sessionActionsViewModel.dismissError()
|
||||
@@ -74,7 +75,7 @@ fun ABoxManagerApp(
|
||||
is AppSessionUiState.Login -> LoginRoute(
|
||||
profile = state.profile,
|
||||
bootstrapError = state.bootstrapError,
|
||||
actionError = actionState.errorMessage,
|
||||
actionError = actionError,
|
||||
actionInProgress = actionState.inProgress,
|
||||
onSwitchProvider = sessionActionsViewModel::switchProvider,
|
||||
viewModel = loginViewModel,
|
||||
@@ -83,7 +84,7 @@ fun ABoxManagerApp(
|
||||
username = state.username,
|
||||
profile = state.profile,
|
||||
actionInProgress = actionState.inProgress,
|
||||
errorMessage = actionState.errorMessage,
|
||||
errorMessage = actionError,
|
||||
onLogout = sessionActionsViewModel::logout,
|
||||
)
|
||||
}
|
||||
|
||||
+1
-8
@@ -13,13 +13,6 @@ object DomainErrorMapper {
|
||||
is DomainError.InvalidRequest -> UiText.StringResource(R.string.error_invalid_request)
|
||||
is DomainError.OperationFailed -> UiText.StringResource(R.string.error_operation_failed)
|
||||
is DomainError.NoData -> UiText.StringResource(R.string.error_no_data)
|
||||
is DomainError.Unknown -> {
|
||||
val detail = error.detail
|
||||
if (!detail.isNullOrBlank()) {
|
||||
UiText.DynamicString(detail)
|
||||
} else {
|
||||
UiText.StringResource(R.string.error_unknown)
|
||||
}
|
||||
}
|
||||
is DomainError.Unknown -> UiText.StringResource(R.string.error_unknown)
|
||||
}
|
||||
}
|
||||
|
||||
+39
-3
@@ -4,6 +4,9 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.flagship.abox.manager.domain.DomainResult
|
||||
import com.flagship.abox.manager.domain.SessionCoordinator
|
||||
import com.flagship.abox.manager.domain.SessionValidity
|
||||
import com.flagship.abox.manager.ui.common.DomainErrorMapper
|
||||
import com.flagship.abox.manager.ui.common.UiText
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -18,9 +21,21 @@ class SessionActionsViewModel @Inject constructor(
|
||||
private val _uiState = MutableStateFlow(SessionActionsUiState())
|
||||
val uiState: StateFlow<SessionActionsUiState> = _uiState.asStateFlow()
|
||||
|
||||
fun onAction(action: SessionUiAction) {
|
||||
when (action) {
|
||||
SessionUiAction.EndSession -> runAction(coordinator::endActiveSession)
|
||||
SessionUiAction.ForgetSavedCredentials -> runAction(coordinator::forgetSavedCredentials)
|
||||
SessionUiAction.SwitchProvider -> runAction(coordinator::switchProvider)
|
||||
SessionUiAction.ValidateConnection -> runValidation()
|
||||
SessionUiAction.DismissError -> dismissError()
|
||||
}
|
||||
}
|
||||
|
||||
fun dispatch(action: SessionUiAction) = onAction(action)
|
||||
|
||||
fun logout() = runAction(coordinator::logout)
|
||||
|
||||
fun switchProvider() = runAction(coordinator::switchProvider)
|
||||
fun switchProvider() = onAction(SessionUiAction.SwitchProvider)
|
||||
|
||||
fun dismissError() {
|
||||
_uiState.value = _uiState.value.copy(errorMessage = null)
|
||||
@@ -32,7 +47,19 @@ class SessionActionsViewModel @Inject constructor(
|
||||
_uiState.value = SessionActionsUiState(inProgress = true)
|
||||
val result = action()
|
||||
_uiState.value = SessionActionsUiState(
|
||||
errorMessage = (result as? DomainResult.Failure)?.error?.detail,
|
||||
errorMessage = (result as? DomainResult.Failure)?.error?.let(DomainErrorMapper::map),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun runValidation() {
|
||||
if (_uiState.value.inProgress) return
|
||||
viewModelScope.launch {
|
||||
_uiState.value = SessionActionsUiState(inProgress = true)
|
||||
val result = coordinator.validateActiveSession()
|
||||
_uiState.value = SessionActionsUiState(
|
||||
validity = (result as? DomainResult.Success)?.value,
|
||||
errorMessage = (result as? DomainResult.Failure)?.error?.let(DomainErrorMapper::map),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -40,5 +67,14 @@ class SessionActionsViewModel @Inject constructor(
|
||||
|
||||
data class SessionActionsUiState(
|
||||
val inProgress: Boolean = false,
|
||||
val errorMessage: String? = null,
|
||||
val validity: SessionValidity? = null,
|
||||
val errorMessage: UiText? = null,
|
||||
)
|
||||
|
||||
sealed interface SessionUiAction {
|
||||
data object EndSession : SessionUiAction
|
||||
data object ForgetSavedCredentials : SessionUiAction
|
||||
data object SwitchProvider : SessionUiAction
|
||||
data object ValidateConnection : SessionUiAction
|
||||
data object DismissError : SessionUiAction
|
||||
}
|
||||
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
package com.flagship.abox.manager.ui.session
|
||||
|
||||
import com.flagship.abox.manager.R
|
||||
import com.flagship.abox.manager.domain.DomainError
|
||||
import com.flagship.abox.manager.domain.LoginCredentials
|
||||
import com.flagship.abox.manager.domain.Password
|
||||
import com.flagship.abox.manager.domain.ProviderProfile
|
||||
import com.flagship.abox.manager.domain.ProviderProfileId
|
||||
import com.flagship.abox.manager.domain.ProviderType
|
||||
import com.flagship.abox.manager.domain.SessionCoordinator
|
||||
import com.flagship.abox.manager.domain.SessionValidity
|
||||
import com.flagship.abox.manager.domain.Username
|
||||
import com.flagship.abox.manager.testing.MainDispatcherRule
|
||||
import com.flagship.abox.manager.testing.fakes.FakeCredentialStore
|
||||
import com.flagship.abox.manager.testing.fakes.FakeLocalSessionGateway
|
||||
import com.flagship.abox.manager.testing.fakes.FakeProviderProfileStore
|
||||
import com.flagship.abox.manager.testing.fakes.FakeSessionGateway
|
||||
import com.flagship.abox.manager.ui.common.UiText
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class SessionActionsViewModelTest {
|
||||
@get:Rule
|
||||
val mainDispatcherRule = MainDispatcherRule()
|
||||
|
||||
private val profile = ProviderProfile(
|
||||
id = ProviderProfileId("abox"),
|
||||
type = ProviderType.ABOX,
|
||||
displayName = "ABox",
|
||||
)
|
||||
private val credentials = LoginCredentials(Username("demo"), Password("secret"))
|
||||
|
||||
@Test
|
||||
fun endSessionActionEndsSessionWithoutForgettingCredentials() = runTest {
|
||||
val fixture = Fixture()
|
||||
fixture.activateAuthenticatedProfile()
|
||||
fixture.coordinator.login(profile.id, credentials, rememberUsername = true, autoLogin = true)
|
||||
val viewModel = SessionActionsViewModel(fixture.coordinator)
|
||||
|
||||
viewModel.onAction(SessionUiAction.EndSession)
|
||||
advanceUntilIdle()
|
||||
|
||||
assertTrue(fixture.remote.session(profile.id) == null)
|
||||
assertEquals(
|
||||
credentials,
|
||||
fixture.credentials.loadCredentials(profile.id).successValue(),
|
||||
)
|
||||
assertNull(viewModel.uiState.value.errorMessage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun forgetCredentialsActionKeepsSessionAndDisablesAutoLogin() = runTest {
|
||||
val fixture = Fixture()
|
||||
fixture.activateAuthenticatedProfile()
|
||||
fixture.coordinator.login(profile.id, credentials, rememberUsername = true, autoLogin = true)
|
||||
val viewModel = SessionActionsViewModel(fixture.coordinator)
|
||||
|
||||
viewModel.onAction(SessionUiAction.ForgetSavedCredentials)
|
||||
advanceUntilIdle()
|
||||
|
||||
assertEquals(profile.id, fixture.remote.session(profile.id)?.profileId)
|
||||
assertNull(fixture.credentials.loadCredentials(profile.id).successValue())
|
||||
assertTrue(!fixture.profiles.loginPreferences(profile.id).autoLoginEnabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchProviderActionClearsProfileOnlyAfterLogout() = runTest {
|
||||
val fixture = Fixture()
|
||||
fixture.activateAuthenticatedProfile()
|
||||
fixture.remote.login(profile.id, credentials)
|
||||
val viewModel = SessionActionsViewModel(fixture.coordinator)
|
||||
|
||||
viewModel.onAction(SessionUiAction.SwitchProvider)
|
||||
advanceUntilIdle()
|
||||
|
||||
assertEquals(profile.id, fixture.remote.logoutProfileId)
|
||||
assertNull(fixture.profiles.activeProfile())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateConnectionActionDelegatesWithoutRelogin() = runTest {
|
||||
val fixture = Fixture()
|
||||
fixture.activateAuthenticatedProfile()
|
||||
fixture.remote.setValidationResult(
|
||||
profile.id,
|
||||
com.flagship.abox.manager.domain.DomainResult.Success(SessionValidity.INVALID),
|
||||
)
|
||||
val viewModel = SessionActionsViewModel(fixture.coordinator)
|
||||
|
||||
viewModel.onAction(SessionUiAction.ValidateConnection)
|
||||
advanceUntilIdle()
|
||||
|
||||
assertEquals(SessionValidity.INVALID, fixture.remote.lastValidation)
|
||||
assertEquals(0, fixture.remote.loginCalls)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mappedActionErrorNeverExposesDomainDetail() = runTest {
|
||||
val fixture = Fixture()
|
||||
fixture.activateAuthenticatedProfile()
|
||||
fixture.remote.logoutError = DomainError.Unknown(detail = "keystore secret detail")
|
||||
val viewModel = SessionActionsViewModel(fixture.coordinator)
|
||||
|
||||
viewModel.dispatch(SessionUiAction.EndSession)
|
||||
advanceUntilIdle()
|
||||
|
||||
assertEquals(
|
||||
UiText.StringResource(R.string.error_unknown),
|
||||
viewModel.uiState.value.errorMessage,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dismissErrorActionClearsMappedError() = runTest {
|
||||
val fixture = Fixture()
|
||||
fixture.activateAuthenticatedProfile()
|
||||
fixture.remote.logoutError = DomainError.Network(detail = "private network detail")
|
||||
val viewModel = SessionActionsViewModel(fixture.coordinator)
|
||||
|
||||
viewModel.onAction(SessionUiAction.EndSession)
|
||||
advanceUntilIdle()
|
||||
viewModel.onAction(SessionUiAction.DismissError)
|
||||
|
||||
assertNull(viewModel.uiState.value.errorMessage)
|
||||
}
|
||||
|
||||
private class Fixture {
|
||||
val profiles = FakeProviderProfileStore()
|
||||
val credentials = FakeCredentialStore()
|
||||
val remote = RecordingSessionGateway(profiles)
|
||||
val local = FakeLocalSessionGateway()
|
||||
val coordinator = SessionCoordinator(profiles, credentials, remote, local)
|
||||
|
||||
suspend fun activateAuthenticatedProfile() {
|
||||
profiles.saveProfile(
|
||||
ProviderProfile(
|
||||
id = ProviderProfileId("abox"),
|
||||
type = ProviderType.ABOX,
|
||||
displayName = "ABox",
|
||||
),
|
||||
)
|
||||
profiles.setActiveProfile(ProviderProfileId("abox"))
|
||||
}
|
||||
}
|
||||
|
||||
private class RecordingSessionGateway(
|
||||
private val profiles: FakeProviderProfileStore,
|
||||
private val delegate: FakeSessionGateway = FakeSessionGateway(),
|
||||
) : com.flagship.abox.manager.domain.SessionGateway by delegate {
|
||||
var loginCalls = 0
|
||||
private set
|
||||
var logoutProfileId: ProviderProfileId? = null
|
||||
private set
|
||||
var lastValidation: SessionValidity? = null
|
||||
private set
|
||||
var logoutError: DomainError? = null
|
||||
|
||||
override suspend fun login(
|
||||
profileId: ProviderProfileId,
|
||||
credentials: LoginCredentials,
|
||||
) = delegate.login(profileId, credentials).also { loginCalls++ }
|
||||
|
||||
override suspend fun validateSession(profileId: ProviderProfileId) = delegate
|
||||
.validateSession(profileId)
|
||||
.also { result ->
|
||||
lastValidation = (result as? com.flagship.abox.manager.domain.DomainResult.Success)?.value
|
||||
}
|
||||
|
||||
override suspend fun logout(profileId: ProviderProfileId) = logoutError?.let {
|
||||
logoutProfileId = profiles.activeProfile()?.id
|
||||
com.flagship.abox.manager.domain.DomainResult.Failure(it)
|
||||
} ?: delegate.logout(profileId).also {
|
||||
logoutProfileId = profiles.activeProfile()?.id
|
||||
}
|
||||
|
||||
suspend fun session(profileId: ProviderProfileId) = delegate.observeSession(profileId)
|
||||
.let { flow -> flow.first() }
|
||||
|
||||
fun setValidationResult(
|
||||
profileId: ProviderProfileId,
|
||||
result: com.flagship.abox.manager.domain.DomainResult<SessionValidity>,
|
||||
) {
|
||||
delegate.setValidationResult(profileId, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> com.flagship.abox.manager.domain.DomainResult<T>.successValue(): T? =
|
||||
(this as? com.flagship.abox.manager.domain.DomainResult.Success)?.value
|
||||
@@ -147,6 +147,35 @@ class SessionCoordinator(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun endActiveSession(): DomainResult<Unit> = storeOperation {
|
||||
val profile = profileStore.activeProfile() ?: return@storeOperation DomainResult.Success(Unit)
|
||||
when (val logoutResult = profile.gateway().logout(profile.id)) {
|
||||
is DomainResult.Failure -> return@storeOperation logoutResult
|
||||
is DomainResult.Success -> Unit
|
||||
}
|
||||
if (profile.type == ProviderType.LOCAL_EXPERIENCE) {
|
||||
profileStore.setActiveProfile(null)
|
||||
} else {
|
||||
DomainResult.Success(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun forgetSavedCredentials(): DomainResult<Unit> = storeOperation {
|
||||
val profile = profileStore.activeProfile() ?: return@storeOperation DomainResult.Success(Unit)
|
||||
when (val credentialResult = credentialStore.clearCredentials(profile.id)) {
|
||||
is DomainResult.Failure -> return@storeOperation credentialResult
|
||||
is DomainResult.Success -> Unit
|
||||
}
|
||||
val preferences = profileStore.loginPreferences(profile.id)
|
||||
profileStore.saveLoginPreferences(preferences.copy(autoLoginEnabled = false))
|
||||
}
|
||||
|
||||
suspend fun validateActiveSession(): DomainResult<SessionValidity> = storeOperation {
|
||||
val profile = profileStore.activeProfile()
|
||||
?: return@storeOperation DomainResult.Success(SessionValidity.INVALID)
|
||||
profile.gateway().validateSession(profile.id)
|
||||
}
|
||||
|
||||
suspend fun logout(): DomainResult<Unit> = storeOperation {
|
||||
val profile = profileStore.activeProfile() ?: return@storeOperation DomainResult.Success(Unit)
|
||||
when (val credentialResult = credentialStore.clearCredentials(profile.id)) {
|
||||
|
||||
+128
@@ -12,6 +12,7 @@ import com.flagship.abox.manager.domain.ProviderType
|
||||
import com.flagship.abox.manager.domain.SessionCoordinator
|
||||
import com.flagship.abox.manager.domain.SessionGateway
|
||||
import com.flagship.abox.manager.domain.Session
|
||||
import com.flagship.abox.manager.domain.SessionValidity
|
||||
import com.flagship.abox.manager.domain.Username
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -133,6 +134,101 @@ class SessionCoordinatorTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun endActiveSessionKeepsAboxCredentials() = runTest {
|
||||
val fixture = Fixture()
|
||||
fixture.coordinator.selectProvider(remoteProfile)
|
||||
fixture.coordinator.login(
|
||||
remoteProfile.id,
|
||||
credentials,
|
||||
rememberUsername = true,
|
||||
autoLogin = true,
|
||||
)
|
||||
|
||||
val result = fixture.coordinator.endActiveSession()
|
||||
|
||||
assertTrue(result is DomainResult.Success)
|
||||
assertEquals(remoteProfile, fixture.profiles.activeProfile())
|
||||
assertNull(fixture.remote.observeSession(remoteProfile.id).first())
|
||||
assertEquals(
|
||||
credentials,
|
||||
(fixture.credentials.loadCredentials(remoteProfile.id) as DomainResult.Success).value,
|
||||
)
|
||||
assertTrue(fixture.profiles.loginPreferences(remoteProfile.id).autoLoginEnabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun forgetSavedCredentialsDisablesAutoLoginAndKeepsSessionStateExplicit() = runTest {
|
||||
val fixture = Fixture()
|
||||
fixture.coordinator.selectProvider(remoteProfile)
|
||||
fixture.coordinator.login(
|
||||
remoteProfile.id,
|
||||
credentials,
|
||||
rememberUsername = true,
|
||||
autoLogin = true,
|
||||
)
|
||||
|
||||
val result = fixture.coordinator.forgetSavedCredentials()
|
||||
|
||||
assertTrue(result is DomainResult.Success)
|
||||
assertEquals(remoteProfile, fixture.profiles.activeProfile())
|
||||
assertEquals(
|
||||
remoteProfile.id,
|
||||
fixture.remote.observeSession(remoteProfile.id).first()?.profileId,
|
||||
)
|
||||
assertNull(
|
||||
(fixture.credentials.loadCredentials(remoteProfile.id) as DomainResult.Success).value,
|
||||
)
|
||||
val preferences = fixture.profiles.loginPreferences(remoteProfile.id)
|
||||
assertEquals(credentials.username, preferences.rememberedUsername)
|
||||
assertTrue(!preferences.autoLoginEnabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchProviderClearsActiveProfileAfterGatewayLogout() = runTest {
|
||||
val profiles = FakeProviderProfileStore()
|
||||
val remote = RecordingSessionGateway(profiles)
|
||||
val coordinator = SessionCoordinator(
|
||||
profileStore = profiles,
|
||||
credentialStore = FakeCredentialStore(),
|
||||
remoteGateway = remote,
|
||||
localGateway = FakeLocalSessionGateway(),
|
||||
)
|
||||
coordinator.selectProvider(remoteProfile)
|
||||
remote.login(remoteProfile.id, credentials)
|
||||
|
||||
val result = coordinator.switchProvider()
|
||||
|
||||
assertTrue(result is DomainResult.Success)
|
||||
assertEquals(remoteProfile.id, remote.activeProfileAtLogout)
|
||||
assertNull(profiles.activeProfile())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateActiveSessionDoesNotRelogin() = runTest {
|
||||
val remote = CountingSessionGateway()
|
||||
val credentialsStore = CountingCredentialStore()
|
||||
val profiles = FakeProviderProfileStore()
|
||||
val coordinator = SessionCoordinator(
|
||||
profileStore = profiles,
|
||||
credentialStore = credentialsStore,
|
||||
remoteGateway = remote,
|
||||
localGateway = FakeLocalSessionGateway(),
|
||||
)
|
||||
profiles.saveProfile(remoteProfile)
|
||||
profiles.setActiveProfile(remoteProfile.id)
|
||||
remote.setValidationResult(
|
||||
remoteProfile.id,
|
||||
DomainResult.Success(SessionValidity.INVALID),
|
||||
)
|
||||
|
||||
val result = coordinator.validateActiveSession()
|
||||
|
||||
assertEquals(DomainResult.Success(SessionValidity.INVALID), result)
|
||||
assertEquals(0, remote.loginCalls)
|
||||
assertEquals(0, credentialsStore.loadCalls)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun authenticationFailureDuringRecoveryClearsSavedCredential() = runTest {
|
||||
val fixture = Fixture()
|
||||
@@ -189,6 +285,18 @@ class SessionCoordinatorTest {
|
||||
assertTrue(!fixture.profiles.loginPreferences(remoteProfile.id).autoLoginEnabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun endActiveSessionClearsLocalActiveProfile() = runTest {
|
||||
val fixture = Fixture()
|
||||
fixture.coordinator.selectProvider(localProfile)
|
||||
|
||||
val result = fixture.coordinator.endActiveSession()
|
||||
|
||||
assertTrue(result is DomainResult.Success)
|
||||
assertNull(fixture.profiles.activeProfile())
|
||||
assertNull(fixture.local.observeSession(localProfile.id).first())
|
||||
}
|
||||
|
||||
private class Fixture {
|
||||
val profiles = FakeProviderProfileStore()
|
||||
val credentials = FakeCredentialStore()
|
||||
@@ -197,6 +305,19 @@ class SessionCoordinatorTest {
|
||||
val coordinator = SessionCoordinator(profiles, credentials, remote, local)
|
||||
}
|
||||
|
||||
private class RecordingSessionGateway(
|
||||
private val profileStore: FakeProviderProfileStore,
|
||||
private val delegate: FakeSessionGateway = FakeSessionGateway(),
|
||||
) : SessionGateway by delegate {
|
||||
var activeProfileAtLogout: ProviderProfileId? = null
|
||||
private set
|
||||
|
||||
override suspend fun logout(profileId: ProviderProfileId): DomainResult<Unit> {
|
||||
activeProfileAtLogout = profileStore.activeProfile()?.id
|
||||
return delegate.logout(profileId)
|
||||
}
|
||||
}
|
||||
|
||||
private class CountingSessionGateway(
|
||||
private val delegate: FakeSessionGateway = FakeSessionGateway(),
|
||||
) : SessionGateway by delegate {
|
||||
@@ -214,6 +335,13 @@ class SessionCoordinatorTest {
|
||||
fun failLogin(profileId: ProviderProfileId, error: DomainError) {
|
||||
delegate.failLogin(profileId, error)
|
||||
}
|
||||
|
||||
fun setValidationResult(
|
||||
profileId: ProviderProfileId,
|
||||
result: DomainResult<SessionValidity>,
|
||||
) {
|
||||
delegate.setValidationResult(profileId, result)
|
||||
}
|
||||
}
|
||||
|
||||
private class CountingCredentialStore(
|
||||
|
||||
Reference in New Issue
Block a user