feat(account-recovery): Add recovery state observer.

This commit is contained in:
dkadrikj
2023-06-16 09:58:14 +02:00
parent 3c4499d45f
commit b4741134d4
6 changed files with 320 additions and 12 deletions
@@ -14,6 +14,10 @@ public final class me/proton/core/accountrecovery/domain/AccountRecoveryState :
public static fun values ()[Lme/proton/core/accountrecovery/domain/AccountRecoveryState;
}
public final class me/proton/core/accountrecovery/domain/AccountRecoveryStateKt {
public static final fun toAccountRecoveryState (Lme/proton/core/domain/type/IntEnum;)Lme/proton/core/accountrecovery/domain/AccountRecoveryState;
}
public abstract interface class me/proton/core/accountrecovery/domain/CancelNotifications {
public abstract fun invoke (Lme/proton/core/domain/entity/UserId;)V
}
@@ -44,7 +48,8 @@ public final class me/proton/core/accountrecovery/domain/usecase/CancelRecovery
}
public final class me/proton/core/accountrecovery/domain/usecase/ObserveAccountRecoveryState {
public fun <init> (Lme/proton/core/user/domain/repository/UserRepository;)V
public fun <init> (Lme/proton/core/user/domain/UserManager;)V
public final fun invoke (Lme/proton/core/domain/entity/UserId;Z)Lkotlinx/coroutines/flow/Flow;
public static synthetic fun invoke$default (Lme/proton/core/accountrecovery/domain/usecase/ObserveAccountRecoveryState;Lme/proton/core/domain/entity/UserId;ZILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
}
+17 -4
View File
@@ -26,13 +26,13 @@ plugins {
protonKotlinLibrary
}
publishOption.shouldBePublishedAsLib = true
protonCoverage {
// change when stubs implemented
minLineCoveragePercentage.set(35)
minBranchCoveragePercentage.set(82)
minLineCoveragePercentage.set(86)
}
publishOption.shouldBePublishedAsLib = true
dependencies {
api(
project(Module.authDomain),
@@ -43,10 +43,23 @@ dependencies {
`javax-inject`
)
implementation(
project(Module.kotlinUtil)
)
testImplementation(
`coroutines-test`,
junit,
`kotlin-test`,
mockk
)
testImplementation(
project(Module.kotlinTest),
`coroutines-test`,
junit,
`kotlin-test`,
mockk,
turbine
)
}
@@ -18,10 +18,23 @@
package me.proton.core.accountrecovery.domain
import me.proton.core.domain.type.IntEnum
import me.proton.core.user.domain.entity.UserRecovery
public enum class AccountRecoveryState {
None,
GracePeriod,
ResetPassword,
Cancelled,
Expired
}
}
public fun IntEnum<UserRecovery.State>.toAccountRecoveryState(): AccountRecoveryState =
when (this.enum) {
UserRecovery.State.None -> AccountRecoveryState.None
UserRecovery.State.Grace -> AccountRecoveryState.GracePeriod
UserRecovery.State.Cancelled -> AccountRecoveryState.Cancelled
UserRecovery.State.Insecure -> AccountRecoveryState.ResetPassword
UserRecovery.State.Expired -> AccountRecoveryState.Expired
null -> AccountRecoveryState.None
}
@@ -21,15 +21,19 @@ package me.proton.core.accountrecovery.domain.usecase
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import me.proton.core.accountrecovery.domain.AccountRecoveryState
import me.proton.core.accountrecovery.domain.IsAccountRecoveryEnabled
import me.proton.core.accountrecovery.domain.toAccountRecoveryState
import me.proton.core.domain.entity.UserId
import me.proton.core.user.domain.repository.UserRepository
import me.proton.core.user.domain.UserManager
import javax.inject.Inject
public class ObserveAccountRecoveryState @Inject constructor(
private val userRepository: UserRepository
private val userManager: UserManager
) {
public operator fun invoke(userId: UserId, refresh: Boolean): Flow<AccountRecoveryState> = userRepository.observeUser(userId).map {
AccountRecoveryState.GracePeriod // todo: hardcoded change later
}
public operator fun invoke(userId: UserId, refresh: Boolean = true): Flow<AccountRecoveryState> =
userManager.observeUser(sessionUserId = userId, refresh = refresh)
.map { user ->
if (user == null) AccountRecoveryState.None
else
user.recovery?.state?.toAccountRecoveryState() ?: AccountRecoveryState.None
}
}
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.accountrecovery.domain
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import me.proton.core.domain.type.IntEnum
import me.proton.core.test.kotlin.assertEquals
import me.proton.core.user.domain.entity.UserRecovery
import org.junit.After
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
class AccountRecoveryStateKtTest {
@Before
fun beforeEveryTest() {
mockkStatic("me.proton.core.accountrecovery.domain.AccountRecoveryStateKt")
}
@After
fun afterEveryTest() {
unmockkStatic("me.proton.core.accountrecovery.domain.AccountRecoveryStateKt")
}
@Test
fun `mapping works correctly`() {
var testState = IntEnum(0, UserRecovery.State.None)
var result = testState.toAccountRecoveryState()
assertEquals(AccountRecoveryState.None, result)
testState = IntEnum(1, UserRecovery.State.Grace)
result = testState.toAccountRecoveryState()
assertEquals(AccountRecoveryState.GracePeriod, result)
testState = IntEnum(2, UserRecovery.State.Cancelled)
result = testState.toAccountRecoveryState()
assertEquals(AccountRecoveryState.Cancelled, result)
testState = IntEnum(3, UserRecovery.State.Insecure)
result = testState.toAccountRecoveryState()
assertEquals(AccountRecoveryState.ResetPassword, result)
testState = IntEnum(4, UserRecovery.State.Expired)
result = testState.toAccountRecoveryState()
assertEquals(AccountRecoveryState.Expired, result)
}
}
@@ -0,0 +1,208 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.accountrecovery.domain.usecase
import app.cash.turbine.test
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import me.proton.core.accountrecovery.domain.AccountRecoveryState
import me.proton.core.domain.entity.UserId
import me.proton.core.domain.type.IntEnum
import me.proton.core.network.domain.session.SessionId
import me.proton.core.user.domain.UserManager
import me.proton.core.user.domain.entity.User
import me.proton.core.user.domain.entity.UserRecovery
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
class ObserveAccountRecoveryStateTest {
private val testUserId = UserId("test-user-id")
private val testUser = User(
userId = testUserId,
email = null,
name = "test-username",
displayName = null,
currency = "CHF",
credit = 0,
usedSpace = 0,
maxSpace = 100,
maxUpload = 100,
role = null,
private = true,
services = 1,
subscribed = 0,
delinquent = null,
recovery = UserRecovery(
state = IntEnum(1, UserRecovery.State.Grace),
startTime = 1L,
endTime = 10L,
sessionId = SessionId("test-session-id"),
reason = UserRecovery.Reason.Authentication
),
keys = emptyList()
)
private val user = MutableStateFlow<User?>(null)
private val observeUser = mockk<UserManager>(relaxed = true) {
coEvery { this@mockk.observeUser(testUserId, any()) } returns user
}
private lateinit var useCase: ObserveAccountRecoveryState
@Before
fun beforeEveryTest() {
useCase = ObserveAccountRecoveryState(observeUser)
}
@Test
fun `null user returns state none`() = runTest {
// WHEN
useCase.invoke(testUserId, true).test {
// THEN
assertEquals(AccountRecoveryState.None, awaitItem())
}
}
@Test
fun `null user returns state none default refresh`() = runTest {
// WHEN
useCase.invoke(testUserId).test {
// THEN
assertEquals(AccountRecoveryState.None, awaitItem())
}
}
@Test
fun `non null user updated after null`() = runTest {
// WHEN
useCase.invoke(testUserId, true).test {
// THEN
assertEquals(AccountRecoveryState.None, awaitItem())
user.emit(testUser)
assertEquals(AccountRecoveryState.GracePeriod, awaitItem())
cancelAndConsumeRemainingEvents()
}
}
@Test
fun `non null user update after null becomes null again`() = runTest {
// WHEN
useCase.invoke(testUserId, true).test {
// THEN
assertEquals(AccountRecoveryState.None, awaitItem())
user.emit(testUser)
assertEquals(AccountRecoveryState.GracePeriod, awaitItem())
user.emit(null)
assertEquals(AccountRecoveryState.None, awaitItem())
cancelAndConsumeRemainingEvents()
}
}
@Test
fun `non null user update after null becomes null again no refresh`() = runTest {
// WHEN
useCase.invoke(testUserId, false).test {
// THEN
assertEquals(AccountRecoveryState.None, awaitItem())
user.emit(testUser)
assertEquals(AccountRecoveryState.GracePeriod, awaitItem())
user.emit(null)
assertEquals(AccountRecoveryState.None, awaitItem())
cancelAndConsumeRemainingEvents()
}
}
@Test
fun `user returns state none`() = runTest {
// WHEN
useCase.invoke(testUserId, false).test {
// THEN
assertEquals(AccountRecoveryState.None, awaitItem())
user.emit(testUser.copy(
recovery = UserRecovery(
state = IntEnum(1, null),
startTime = 1L,
endTime = 10L,
sessionId = SessionId("test-session-id"),
reason = UserRecovery.Reason.Authentication
)
))
assertEquals(AccountRecoveryState.None, awaitItem())
cancelAndConsumeRemainingEvents()
}
}
@Test
fun `non null user update after null becomes null again ResetPassword state`() = runTest {
// WHEN
useCase.invoke(testUserId, false).test {
// THEN
assertEquals(AccountRecoveryState.None, awaitItem())
user.emit(testUser.copy(
recovery = UserRecovery(
state = IntEnum(1, UserRecovery.State.Insecure),
startTime = 1L,
endTime = 10L,
sessionId = SessionId("test-session-id"),
reason = UserRecovery.Reason.Authentication
)
))
assertEquals(AccountRecoveryState.ResetPassword, awaitItem())
user.emit(null)
assertEquals(AccountRecoveryState.None, awaitItem())
cancelAndConsumeRemainingEvents()
}
}
@Test
fun `non null user update after null becomes null again Cancelled state`() = runTest {
// WHEN
useCase.invoke(testUserId, false).test {
// THEN
assertEquals(AccountRecoveryState.None, awaitItem())
user.emit(testUser.copy(
recovery = UserRecovery(
state = IntEnum(1, UserRecovery.State.Cancelled),
startTime = 1L,
endTime = 10L,
sessionId = SessionId("test-session-id"),
reason = UserRecovery.Reason.Authentication
)
))
assertEquals(AccountRecoveryState.Cancelled, awaitItem())
user.emit(null)
assertEquals(AccountRecoveryState.None, awaitItem())
cancelAndConsumeRemainingEvents()
}
}
}