chore(feature-flag): Group all call to WorkManager in FeatureFlagWorkerManager.
Extract interface from FeatureFlagWorkerManager.
This commit is contained in:
parent
884cfa467f
commit
49a40b1f15
|
@ -14,6 +14,7 @@ public abstract interface class me/proton/core/featureflag/dagger/CoreFeatureFla
|
|||
public abstract fun bindFeatureFlagRemoteDataSource (Lme/proton/core/featureflag/data/remote/FeatureFlagRemoteDataSourceImpl;)Lme/proton/core/featureflag/domain/repository/FeatureFlagRemoteDataSource;
|
||||
public abstract fun bindManager (Lme/proton/core/featureflag/data/FeatureFlagManagerImpl;)Lme/proton/core/featureflag/domain/FeatureFlagManager;
|
||||
public abstract fun bindRepository (Lme/proton/core/featureflag/data/repository/FeatureFlagRepositoryImpl;)Lme/proton/core/featureflag/domain/repository/FeatureFlagRepository;
|
||||
public abstract fun bindWorkerManager (Lme/proton/core/featureflag/data/remote/worker/FeatureFlagWorkerManagerImpl;)Lme/proton/core/featureflag/domain/FeatureFlagWorkerManager;
|
||||
public abstract fun optionalFeatureFlagContextProvider ()Lme/proton/core/featureflag/domain/repository/FeatureFlagContextProvider;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,10 @@ import dagger.hilt.components.SingletonComponent
|
|||
import me.proton.core.featureflag.data.FeatureFlagManagerImpl
|
||||
import me.proton.core.featureflag.data.local.FeatureFlagLocalDataSourceImpl
|
||||
import me.proton.core.featureflag.data.remote.FeatureFlagRemoteDataSourceImpl
|
||||
import me.proton.core.featureflag.data.remote.worker.FeatureFlagWorkerManagerImpl
|
||||
import me.proton.core.featureflag.data.repository.FeatureFlagRepositoryImpl
|
||||
import me.proton.core.featureflag.domain.FeatureFlagManager
|
||||
import me.proton.core.featureflag.domain.FeatureFlagWorkerManager
|
||||
import me.proton.core.featureflag.domain.repository.FeatureFlagContextProvider
|
||||
import me.proton.core.featureflag.domain.repository.FeatureFlagLocalDataSource
|
||||
import me.proton.core.featureflag.domain.repository.FeatureFlagRemoteDataSource
|
||||
|
@ -54,6 +56,10 @@ public interface CoreFeatureFlagModule {
|
|||
@Singleton
|
||||
public fun bindManager(featureFlagManagerImpl: FeatureFlagManagerImpl): FeatureFlagManager
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
public fun bindWorkerManager(featureFlagWorkerManagerImpl: FeatureFlagWorkerManagerImpl): FeatureFlagWorkerManager
|
||||
|
||||
@BindsOptionalOf
|
||||
public fun optionalFeatureFlagContextProvider(): FeatureFlagContextProvider
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ public final class me/proton/core/featureflag/data/FeatureFlagManagerImpl_Factor
|
|||
}
|
||||
|
||||
public final class me/proton/core/featureflag/data/FeatureFlagRefreshStarter {
|
||||
public fun <init> (Lme/proton/core/featureflag/data/remote/worker/FeatureFlagWorkerManager;Lme/proton/core/accountmanager/domain/AccountManager;Lme/proton/core/util/kotlin/CoroutineScopeProvider;)V
|
||||
public fun <init> (Lme/proton/core/featureflag/domain/FeatureFlagWorkerManager;Lme/proton/core/accountmanager/domain/AccountManager;Lme/proton/core/util/kotlin/CoroutineScopeProvider;)V
|
||||
public final fun start (Z)V
|
||||
public static synthetic fun start$default (Lme/proton/core/featureflag/data/FeatureFlagRefreshStarter;ZILjava/lang/Object;)V
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ public final class me/proton/core/featureflag/data/FeatureFlagRefreshStarter_Fac
|
|||
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/featureflag/data/FeatureFlagRefreshStarter_Factory;
|
||||
public synthetic fun get ()Ljava/lang/Object;
|
||||
public fun get ()Lme/proton/core/featureflag/data/FeatureFlagRefreshStarter;
|
||||
public static fun newInstance (Lme/proton/core/featureflag/data/remote/worker/FeatureFlagWorkerManager;Lme/proton/core/accountmanager/domain/AccountManager;Lme/proton/core/util/kotlin/CoroutineScopeProvider;)Lme/proton/core/featureflag/data/FeatureFlagRefreshStarter;
|
||||
public static fun newInstance (Lme/proton/core/featureflag/domain/FeatureFlagWorkerManager;Lme/proton/core/accountmanager/domain/AccountManager;Lme/proton/core/util/kotlin/CoroutineScopeProvider;)Lme/proton/core/featureflag/data/FeatureFlagRefreshStarter;
|
||||
}
|
||||
|
||||
public abstract class me/proton/core/featureflag/data/IsFeatureFlagEnabledImpl : me/proton/core/featureflag/domain/IsFeatureFlagEnabled {
|
||||
|
@ -114,19 +114,18 @@ public final class me/proton/core/featureflag/data/local/FeatureFlagLocalDataSou
|
|||
}
|
||||
|
||||
public final class me/proton/core/featureflag/data/remote/FeatureFlagRemoteDataSourceImpl : me/proton/core/featureflag/domain/repository/FeatureFlagRemoteDataSource {
|
||||
public fun <init> (Lme/proton/core/network/data/ApiProvider;Landroidx/work/WorkManager;Ljava/util/Optional;)V
|
||||
public fun <init> (Lme/proton/core/network/data/ApiProvider;Ljava/util/Optional;)V
|
||||
public fun get (Lme/proton/core/domain/entity/UserId;Ljava/util/Set;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun getAll (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun prefetch (Lme/proton/core/domain/entity/UserId;Ljava/util/Set;)V
|
||||
public fun update (Lme/proton/core/featureflag/domain/entity/FeatureFlag;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun update (Lme/proton/core/domain/entity/UserId;Lme/proton/core/featureflag/domain/entity/FeatureId;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class me/proton/core/featureflag/data/remote/FeatureFlagRemoteDataSourceImpl_Factory : dagger/internal/Factory {
|
||||
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)V
|
||||
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/featureflag/data/remote/FeatureFlagRemoteDataSourceImpl_Factory;
|
||||
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;)V
|
||||
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/featureflag/data/remote/FeatureFlagRemoteDataSourceImpl_Factory;
|
||||
public synthetic fun get ()Ljava/lang/Object;
|
||||
public fun get ()Lme/proton/core/featureflag/data/remote/FeatureFlagRemoteDataSourceImpl;
|
||||
public static fun newInstance (Lme/proton/core/network/data/ApiProvider;Landroidx/work/WorkManager;Ljava/util/Optional;)Lme/proton/core/featureflag/data/remote/FeatureFlagRemoteDataSourceImpl;
|
||||
public static fun newInstance (Lme/proton/core/network/data/ApiProvider;Ljava/util/Optional;)Lme/proton/core/featureflag/data/remote/FeatureFlagRemoteDataSourceImpl;
|
||||
}
|
||||
|
||||
public final class me/proton/core/featureflag/data/remote/request/PutFeatureFlagBody {
|
||||
|
@ -275,19 +274,21 @@ public final class me/proton/core/featureflag/data/remote/response/GetUnleashTog
|
|||
public final fun serializer ()Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/featureflag/data/remote/worker/FeatureFlagWorkerManager {
|
||||
public final class me/proton/core/featureflag/data/remote/worker/FeatureFlagWorkerManagerImpl : me/proton/core/featureflag/domain/FeatureFlagWorkerManager {
|
||||
public fun <init> (Landroid/content/Context;Landroidx/work/WorkManager;)V
|
||||
public final fun cancel (Lme/proton/core/domain/entity/UserId;)V
|
||||
public final fun enqueueOneTime (Lme/proton/core/domain/entity/UserId;)V
|
||||
public final fun enqueuePeriodic (Lme/proton/core/domain/entity/UserId;Z)V
|
||||
public fun cancel (Lme/proton/core/domain/entity/UserId;)V
|
||||
public fun enqueueOneTime (Lme/proton/core/domain/entity/UserId;)V
|
||||
public fun enqueuePeriodic (Lme/proton/core/domain/entity/UserId;Z)V
|
||||
public fun prefetch (Lme/proton/core/domain/entity/UserId;Ljava/util/Set;)V
|
||||
public fun update (Lme/proton/core/featureflag/domain/entity/FeatureFlag;)V
|
||||
}
|
||||
|
||||
public final class me/proton/core/featureflag/data/remote/worker/FeatureFlagWorkerManager_Factory : dagger/internal/Factory {
|
||||
public final class me/proton/core/featureflag/data/remote/worker/FeatureFlagWorkerManagerImpl_Factory : dagger/internal/Factory {
|
||||
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;)V
|
||||
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/featureflag/data/remote/worker/FeatureFlagWorkerManager_Factory;
|
||||
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/featureflag/data/remote/worker/FeatureFlagWorkerManagerImpl_Factory;
|
||||
public synthetic fun get ()Ljava/lang/Object;
|
||||
public fun get ()Lme/proton/core/featureflag/data/remote/worker/FeatureFlagWorkerManager;
|
||||
public static fun newInstance (Landroid/content/Context;Landroidx/work/WorkManager;)Lme/proton/core/featureflag/data/remote/worker/FeatureFlagWorkerManager;
|
||||
public fun get ()Lme/proton/core/featureflag/data/remote/worker/FeatureFlagWorkerManagerImpl;
|
||||
public static fun newInstance (Landroid/content/Context;Landroidx/work/WorkManager;)Lme/proton/core/featureflag/data/remote/worker/FeatureFlagWorkerManagerImpl;
|
||||
}
|
||||
|
||||
public abstract interface class me/proton/core/featureflag/data/remote/worker/FetchFeatureIdsWorker_AssistedFactory : androidx/hilt/work/WorkerAssistedFactory {
|
||||
|
@ -343,7 +344,7 @@ public final class me/proton/core/featureflag/data/remote/worker/UpdateFeatureFl
|
|||
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;)V
|
||||
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/featureflag/data/remote/worker/UpdateFeatureFlagWorker_Factory;
|
||||
public fun get (Landroid/content/Context;Landroidx/work/WorkerParameters;)Lme/proton/core/featureflag/data/remote/worker/UpdateFeatureFlagWorker;
|
||||
public static fun newInstance (Landroid/content/Context;Landroidx/work/WorkerParameters;Lme/proton/core/network/data/ApiProvider;Lme/proton/core/featureflag/domain/repository/FeatureFlagLocalDataSource;)Lme/proton/core/featureflag/data/remote/worker/UpdateFeatureFlagWorker;
|
||||
public static fun newInstance (Landroid/content/Context;Landroidx/work/WorkerParameters;Lme/proton/core/featureflag/domain/repository/FeatureFlagRemoteDataSource;Lme/proton/core/featureflag/domain/repository/FeatureFlagLocalDataSource;)Lme/proton/core/featureflag/data/remote/worker/UpdateFeatureFlagWorker;
|
||||
}
|
||||
|
||||
public abstract interface class me/proton/core/featureflag/data/remote/worker/UpdateFeatureFlagWorker_HiltModule {
|
||||
|
@ -374,6 +375,6 @@ public final class me/proton/core/featureflag/data/repository/FeatureFlagReposit
|
|||
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/featureflag/data/repository/FeatureFlagRepositoryImpl_Factory;
|
||||
public synthetic fun get ()Ljava/lang/Object;
|
||||
public fun get ()Lme/proton/core/featureflag/data/repository/FeatureFlagRepositoryImpl;
|
||||
public static fun newInstance (Lme/proton/core/featureflag/domain/repository/FeatureFlagLocalDataSource;Lme/proton/core/featureflag/domain/repository/FeatureFlagRemoteDataSource;Lme/proton/core/featureflag/data/remote/worker/FeatureFlagWorkerManager;Lme/proton/core/observability/domain/ObservabilityManager;Lme/proton/core/util/kotlin/CoroutineScopeProvider;)Lme/proton/core/featureflag/data/repository/FeatureFlagRepositoryImpl;
|
||||
public static fun newInstance (Lme/proton/core/featureflag/domain/repository/FeatureFlagLocalDataSource;Lme/proton/core/featureflag/domain/repository/FeatureFlagRemoteDataSource;Lme/proton/core/featureflag/domain/FeatureFlagWorkerManager;Lme/proton/core/observability/domain/ObservabilityManager;Lme/proton/core/util/kotlin/CoroutineScopeProvider;)Lme/proton/core/featureflag/data/repository/FeatureFlagRepositoryImpl;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ plugins {
|
|||
|
||||
protonCoverage {
|
||||
branchCoveragePercentage.set(50)
|
||||
lineCoveragePercentage.set(83)
|
||||
lineCoveragePercentage.set(82)
|
||||
}
|
||||
|
||||
protonDagger {
|
||||
|
|
|
@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import me.proton.core.account.domain.entity.AccountState
|
||||
import me.proton.core.accountmanager.domain.AccountManager
|
||||
import me.proton.core.featureflag.data.remote.worker.FeatureFlagWorkerManager
|
||||
import me.proton.core.featureflag.domain.FeatureFlagWorkerManager
|
||||
import me.proton.core.util.kotlin.CoroutineScopeProvider
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
|
|
@ -18,13 +18,10 @@
|
|||
|
||||
package me.proton.core.featureflag.data.remote
|
||||
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.WorkManager
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.featureflag.data.remote.request.PutFeatureFlagBody
|
||||
import me.proton.core.featureflag.data.remote.response.toFeatureFlag
|
||||
import me.proton.core.featureflag.domain.LogTag
|
||||
import me.proton.core.featureflag.data.remote.worker.FetchFeatureIdsWorker
|
||||
import me.proton.core.featureflag.data.remote.worker.UpdateFeatureFlagWorker
|
||||
import me.proton.core.featureflag.domain.entity.FeatureFlag
|
||||
import me.proton.core.featureflag.domain.entity.FeatureId
|
||||
import me.proton.core.featureflag.domain.repository.FeatureFlagContextProvider
|
||||
|
@ -37,7 +34,6 @@ import kotlin.jvm.optionals.getOrNull
|
|||
|
||||
public class FeatureFlagRemoteDataSourceImpl @Inject constructor(
|
||||
private val apiProvider: ApiProvider,
|
||||
private val workManager: WorkManager,
|
||||
private val featureFlagContextProvider: Optional<FeatureFlagContextProvider>,
|
||||
) : FeatureFlagRemoteDataSource {
|
||||
|
||||
|
@ -62,20 +58,9 @@ public class FeatureFlagRemoteDataSourceImpl @Inject constructor(
|
|||
}
|
||||
}.valueOrThrow
|
||||
|
||||
override suspend fun update(featureFlag: FeatureFlag) {
|
||||
val request = UpdateFeatureFlagWorker.getRequest(
|
||||
featureFlag.userId,
|
||||
featureFlag.featureId,
|
||||
featureFlag.value
|
||||
)
|
||||
workManager.enqueue(request)
|
||||
}
|
||||
|
||||
override fun prefetch(userId: UserId?, featureIds: Set<FeatureId>) {
|
||||
workManager.enqueueUniqueWork(
|
||||
FetchFeatureIdsWorker.getUniqueWorkName(userId),
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
FetchFeatureIdsWorker.getRequest(userId, featureIds),
|
||||
)
|
||||
override suspend fun update(userId: UserId?, featureId: FeatureId, enabled: Boolean) {
|
||||
apiProvider.get<FeaturesApi>(userId).invoke {
|
||||
putFeatureFlag(featureId.id, PutFeatureFlagBody(enabled))
|
||||
}.valueOrThrow
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,18 +7,21 @@ import androidx.work.WorkManager
|
|||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.featureflag.data.R
|
||||
import me.proton.core.featureflag.domain.FeatureFlagWorkerManager
|
||||
import me.proton.core.featureflag.domain.entity.FeatureFlag
|
||||
import me.proton.core.featureflag.domain.entity.FeatureId
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
|
||||
public class FeatureFlagWorkerManager @Inject constructor(
|
||||
public class FeatureFlagWorkerManagerImpl @Inject constructor(
|
||||
@ApplicationContext
|
||||
private val context: Context,
|
||||
private val workManager: WorkManager
|
||||
) {
|
||||
) : FeatureFlagWorkerManager {
|
||||
|
||||
public fun enqueueOneTime(userId: UserId?) {
|
||||
override fun enqueueOneTime(userId: UserId?) {
|
||||
workManager.enqueueUniqueWork(
|
||||
FetchUnleashTogglesWorker.getOneTimeUniqueWorkName(userId),
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
|
@ -26,7 +29,7 @@ public class FeatureFlagWorkerManager @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
public fun enqueuePeriodic(userId: UserId?, immediately: Boolean) {
|
||||
override fun enqueuePeriodic(userId: UserId?, immediately: Boolean) {
|
||||
val repeatInterval = when (userId) {
|
||||
null -> getRepeatIntervalBackgroundUnauth()
|
||||
else -> getRepeatIntervalBackgroundAuth()
|
||||
|
@ -38,11 +41,29 @@ public class FeatureFlagWorkerManager @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
public fun cancel(userId: UserId?) {
|
||||
override fun cancel(userId: UserId?) {
|
||||
workManager.cancelUniqueWork(FetchUnleashTogglesWorker.getPeriodicUniqueWorkName(userId))
|
||||
workManager.cancelUniqueWork(FetchUnleashTogglesWorker.getOneTimeUniqueWorkName(userId))
|
||||
}
|
||||
|
||||
|
||||
override fun update(featureFlag: FeatureFlag) {
|
||||
val request = UpdateFeatureFlagWorker.getRequest(
|
||||
featureFlag.userId,
|
||||
featureFlag.featureId,
|
||||
featureFlag.value
|
||||
)
|
||||
workManager.enqueue(request)
|
||||
}
|
||||
|
||||
override fun prefetch(userId: UserId?, featureIds: Set<FeatureId>) {
|
||||
workManager.enqueueUniqueWork(
|
||||
FetchFeatureIdsWorker.getUniqueWorkName(userId),
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
FetchFeatureIdsWorker.getRequest(userId, featureIds),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getRepeatIntervalBackgroundAuth(): Duration = context.resources.getInteger(
|
||||
R.integer.core_feature_feature_flag_worker_repeat_interval_auth_seconds
|
||||
).toDuration(DurationUnit.SECONDS)
|
|
@ -32,38 +32,34 @@ import androidx.work.workDataOf
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.featureflag.data.remote.FeaturesApi
|
||||
import me.proton.core.featureflag.data.remote.request.PutFeatureFlagBody
|
||||
import me.proton.core.featureflag.domain.entity.FeatureId
|
||||
import me.proton.core.featureflag.domain.repository.FeatureFlagLocalDataSource
|
||||
import me.proton.core.network.data.ApiProvider
|
||||
import me.proton.core.network.domain.ApiResult
|
||||
import me.proton.core.featureflag.domain.repository.FeatureFlagRemoteDataSource
|
||||
import me.proton.core.network.domain.ApiException
|
||||
import me.proton.core.network.domain.isRetryable
|
||||
|
||||
@HiltWorker
|
||||
internal class UpdateFeatureFlagWorker @AssistedInject constructor(
|
||||
@Assisted context: Context,
|
||||
@Assisted params: WorkerParameters,
|
||||
private val apiProvider: ApiProvider,
|
||||
private val remoteDataSource: FeatureFlagRemoteDataSource,
|
||||
private val localDataSource: FeatureFlagLocalDataSource
|
||||
) : CoroutineWorker(context, params) {
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val userId = inputData.getString(INPUT_USER_ID)?.let { UserId(it) }
|
||||
val featureId = inputData.getString(INPUT_FEATURE_ID) ?: return Result.failure()
|
||||
val featureId = inputData.getString(INPUT_FEATURE_ID)?.let(::FeatureId) ?: return Result.failure()
|
||||
val isEnabled = inputData.getBoolean(INPUT_FEATURE_VALUE, false)
|
||||
|
||||
val apiManager = apiProvider.get<FeaturesApi>(userId)
|
||||
val body = PutFeatureFlagBody(isEnabled)
|
||||
return when (val result = apiManager { putFeatureFlag(featureId, body) }) {
|
||||
is ApiResult.Success -> Result.success()
|
||||
is ApiResult.Error -> {
|
||||
if (result.isRetryable()) {
|
||||
Result.retry()
|
||||
} else {
|
||||
rollbackLocalFeatureFlag(userId, FeatureId(featureId), isEnabled)
|
||||
Result.failure()
|
||||
}
|
||||
return runCatching {
|
||||
remoteDataSource.update(userId, featureId, isEnabled)
|
||||
Result.success()
|
||||
}.getOrElse { error ->
|
||||
if (error is ApiException && error.isRetryable()) {
|
||||
Result.retry()
|
||||
} else {
|
||||
rollbackLocalFeatureFlag(userId, featureId, isEnabled)
|
||||
Result.failure()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import kotlinx.coroutines.sync.Mutex
|
|||
import kotlinx.coroutines.sync.withLock
|
||||
import me.proton.core.data.arch.buildProtonStore
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.featureflag.data.remote.worker.FeatureFlagWorkerManager
|
||||
import me.proton.core.featureflag.domain.FeatureFlagWorkerManager
|
||||
import me.proton.core.featureflag.domain.entity.FeatureFlag
|
||||
import me.proton.core.featureflag.domain.entity.FeatureId
|
||||
import me.proton.core.featureflag.domain.entity.Scope
|
||||
|
@ -154,11 +154,11 @@ public class FeatureFlagRepositoryImpl @Inject internal constructor(
|
|||
get(userId, setOf(featureId), refresh).firstOrNull()
|
||||
|
||||
override fun prefetch(userId: UserId?, featureIds: Set<FeatureId>) {
|
||||
remoteDataSource.prefetch(userId, featureIds)
|
||||
workerManager.prefetch(userId, featureIds)
|
||||
}
|
||||
|
||||
override suspend fun update(featureFlag: FeatureFlag) {
|
||||
localDataSource.upsert(listOf(featureFlag))
|
||||
remoteDataSource.update(featureFlag)
|
||||
workerManager.update(featureFlag)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,8 +27,7 @@ import me.proton.core.account.domain.entity.Account
|
|||
import me.proton.core.account.domain.entity.AccountState
|
||||
import me.proton.core.accountmanager.domain.AccountManager
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.featureflag.data.remote.worker.FeatureFlagWorkerManager
|
||||
import me.proton.core.featureflag.domain.ExperimentalProtonFeatureFlag
|
||||
import me.proton.core.featureflag.domain.FeatureFlagWorkerManager
|
||||
import me.proton.core.test.kotlin.UnconfinedTestCoroutineScopeProvider
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Proton AG
|
||||
*
|
||||
* This file is part of Proton Mail.
|
||||
*
|
||||
* Proton Mail 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.
|
||||
*
|
||||
* Proton Mail 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 Proton Mail. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package me.proton.core.featureflag.data.remote
|
||||
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import me.proton.core.featureflag.data.remote.worker.FetchFeatureIdsWorker
|
||||
import me.proton.core.featureflag.data.remote.worker.UpdateFeatureFlagWorker
|
||||
import me.proton.core.featureflag.data.repository.TestFeatureFlagContextProvider
|
||||
import me.proton.core.featureflag.data.testdata.FeatureFlagTestData
|
||||
import me.proton.core.featureflag.data.testdata.UserIdTestData
|
||||
import me.proton.core.featureflag.domain.repository.FeatureFlagContextProvider
|
||||
import me.proton.core.network.data.ApiProvider
|
||||
import org.junit.Test
|
||||
import java.util.Optional
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class FeatureFlagRemoteDataSourceImplTest {
|
||||
|
||||
private val workManager: WorkManager = mockk {
|
||||
coEvery { enqueue(any<OneTimeWorkRequest>()) } returns mockk()
|
||||
coEvery { enqueueUniqueWork(any(), any(), any<OneTimeWorkRequest>()) } returns mockk()
|
||||
}
|
||||
private val apiProvider: ApiProvider = mockk()
|
||||
|
||||
private val remoteDataSource = FeatureFlagRemoteDataSourceImpl(apiProvider, workManager, Optional.empty())
|
||||
|
||||
@Test
|
||||
fun `update enqueues worker to update on remote`() = runTest {
|
||||
// given
|
||||
val featureFlag = FeatureFlagTestData.disabledFeature
|
||||
|
||||
// when
|
||||
remoteDataSource.update(featureFlag)
|
||||
|
||||
// then
|
||||
val requestSlot = slot<OneTimeWorkRequest>()
|
||||
verify { workManager.enqueue(capture(requestSlot)) }
|
||||
val workSpec = requestSlot.captured.workSpec
|
||||
assertEquals(UpdateFeatureFlagWorker::class.qualifiedName, workSpec.workerClassName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `prefetch enqueues worker to prefetch on remote`() = runTest {
|
||||
// given
|
||||
val featureIds = setOf(FeatureFlagTestData.featureId, FeatureFlagTestData.featureId1)
|
||||
val userId = UserIdTestData.userId
|
||||
|
||||
// when
|
||||
remoteDataSource.prefetch(userId, featureIds)
|
||||
|
||||
// then
|
||||
val requestSlot = slot<OneTimeWorkRequest>()
|
||||
val expectedName = FetchFeatureIdsWorker.getUniqueWorkName(userId)
|
||||
verify { workManager.enqueueUniqueWork(expectedName, ExistingWorkPolicy.REPLACE, capture(requestSlot)) }
|
||||
val workSpec = requestSlot.captured.workSpec
|
||||
assertEquals(FetchFeatureIdsWorker::class.qualifiedName, workSpec.workerClassName)
|
||||
}
|
||||
}
|
|
@ -26,12 +26,15 @@ import androidx.work.PeriodicWorkRequest
|
|||
import androidx.work.WorkManager
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import me.proton.core.featureflag.data.testdata.FeatureFlagTestData
|
||||
import me.proton.core.featureflag.data.testdata.UserIdTestData
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class FeatureFlagWorkerManagerTest {
|
||||
class FeatureFlagWorkerManagerImplTest {
|
||||
|
||||
private val userId = UserIdTestData.userId
|
||||
|
||||
|
@ -42,7 +45,7 @@ class FeatureFlagWorkerManagerTest {
|
|||
}
|
||||
private val workManager = mockk<WorkManager>(relaxed = true)
|
||||
|
||||
private fun mockManager() = FeatureFlagWorkerManager(context, workManager)
|
||||
private fun mockManager() = FeatureFlagWorkerManagerImpl(context, workManager)
|
||||
|
||||
@Test
|
||||
fun enqueueOneTime() = runTest {
|
||||
|
@ -88,4 +91,36 @@ class FeatureFlagWorkerManagerTest {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `update enqueues worker to update on remote`() = runTest {
|
||||
// given
|
||||
val featureFlag = FeatureFlagTestData.disabledFeature
|
||||
|
||||
// when
|
||||
mockManager().update(featureFlag)
|
||||
|
||||
// then
|
||||
val requestSlot = slot<OneTimeWorkRequest>()
|
||||
verify { workManager.enqueue(capture(requestSlot)) }
|
||||
val workSpec = requestSlot.captured.workSpec
|
||||
assertEquals(UpdateFeatureFlagWorker::class.qualifiedName, workSpec.workerClassName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `prefetch enqueues worker to prefetch on remote`() = runTest {
|
||||
// given
|
||||
val featureIds = setOf(FeatureFlagTestData.featureId, FeatureFlagTestData.featureId1)
|
||||
val userId = UserIdTestData.userId
|
||||
|
||||
// when
|
||||
mockManager().prefetch(userId, featureIds)
|
||||
|
||||
// then
|
||||
val requestSlot = slot<OneTimeWorkRequest>()
|
||||
val expectedName = FetchFeatureIdsWorker.getUniqueWorkName(userId)
|
||||
verify { workManager.enqueueUniqueWork(expectedName, ExistingWorkPolicy.REPLACE, capture(requestSlot)) }
|
||||
val workSpec = requestSlot.captured.workSpec
|
||||
assertEquals(FetchFeatureIdsWorker::class.qualifiedName, workSpec.workerClassName)
|
||||
}
|
||||
}
|
|
@ -27,27 +27,22 @@ import io.mockk.every
|
|||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.SerializationException
|
||||
import me.proton.core.featureflag.data.remote.FeaturesApi
|
||||
import me.proton.core.featureflag.data.remote.request.PutFeatureFlagBody
|
||||
import me.proton.core.featureflag.data.remote.response.PutFeatureResponse
|
||||
import me.proton.core.featureflag.data.testdata.UserIdTestData
|
||||
import me.proton.core.featureflag.domain.entity.FeatureId
|
||||
import me.proton.core.featureflag.domain.repository.FeatureFlagLocalDataSource
|
||||
import me.proton.core.network.data.ApiProvider
|
||||
import me.proton.core.featureflag.domain.repository.FeatureFlagRemoteDataSource
|
||||
import me.proton.core.network.domain.ApiException
|
||||
import me.proton.core.network.domain.ApiResult
|
||||
import me.proton.core.test.kotlin.CoroutinesTest
|
||||
import me.proton.core.test.kotlin.UnconfinedCoroutinesTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import java.net.UnknownHostException
|
||||
|
||||
class UpdateFeatureFlagWorkerTest : CoroutinesTest by UnconfinedCoroutinesTest() {
|
||||
|
||||
private val userId = UserIdTestData.userId
|
||||
private val featureId = FeatureId("feature-flag-mail")
|
||||
private val featureFlagValue = true
|
||||
private val nonRetryableException = SerializationException()
|
||||
private val retryableException = UnknownHostException()
|
||||
|
||||
private val context = mockk<Context>()
|
||||
private val parameters = mockk<WorkerParameters> {
|
||||
|
@ -56,27 +51,13 @@ class UpdateFeatureFlagWorkerTest : CoroutinesTest by UnconfinedCoroutinesTest()
|
|||
every { inputData.getBoolean(UpdateFeatureFlagWorker.INPUT_FEATURE_VALUE, false) } returns featureFlagValue
|
||||
every { this@mockk.taskExecutor } returns mockk(relaxed = true)
|
||||
}
|
||||
private val featuresApi: FeaturesApi = mockk()
|
||||
private val apiProvider: ApiProvider = mockk {
|
||||
coEvery { get<FeaturesApi>(userId).invoke<PutFeatureResponse>(block = any()) } coAnswers {
|
||||
val block = firstArg<suspend FeaturesApi.() -> PutFeatureResponse>()
|
||||
try {
|
||||
ApiResult.Success(block(featuresApi))
|
||||
} catch (e: Exception) {
|
||||
when (e) {
|
||||
nonRetryableException -> ApiResult.Error.Parse(e)
|
||||
retryableException -> ApiResult.Error.Connection()
|
||||
else -> throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private val remoteDataSource: FeatureFlagRemoteDataSource = mockk()
|
||||
private val localDataSource: FeatureFlagLocalDataSource = mockk(relaxUnitFun = true)
|
||||
|
||||
private val worker = UpdateFeatureFlagWorker(
|
||||
context,
|
||||
parameters,
|
||||
apiProvider,
|
||||
remoteDataSource,
|
||||
localDataSource
|
||||
)
|
||||
|
||||
|
@ -113,7 +94,7 @@ class UpdateFeatureFlagWorkerTest : CoroutinesTest by UnconfinedCoroutinesTest()
|
|||
@Test
|
||||
fun `worker returns success when API calls succeeds`() = runTest {
|
||||
// given
|
||||
coEvery { featuresApi.putFeatureFlag(featureId.id, PutFeatureFlagBody(featureFlagValue)) } returns mockk()
|
||||
coEvery { remoteDataSource.update(userId, featureId, featureFlagValue) } returns mockk()
|
||||
|
||||
// when
|
||||
val actual = worker.doWork()
|
||||
|
@ -126,11 +107,8 @@ class UpdateFeatureFlagWorkerTest : CoroutinesTest by UnconfinedCoroutinesTest()
|
|||
fun `worker returns retry when API calls fails with retryable exception`() = runTest {
|
||||
// given
|
||||
coEvery {
|
||||
featuresApi.putFeatureFlag(
|
||||
featureId.id,
|
||||
PutFeatureFlagBody(featureFlagValue)
|
||||
)
|
||||
} throws retryableException
|
||||
remoteDataSource.update(userId, featureId, featureFlagValue)
|
||||
} throws ApiException(ApiResult.Error.NoInternet())
|
||||
|
||||
// when
|
||||
val actual = worker.doWork()
|
||||
|
@ -143,33 +121,14 @@ class UpdateFeatureFlagWorkerTest : CoroutinesTest by UnconfinedCoroutinesTest()
|
|||
fun `worker returns failure when API calls fails with non retryable exception`() = runTest {
|
||||
// given
|
||||
coEvery {
|
||||
featuresApi.putFeatureFlag(
|
||||
featureId.id,
|
||||
PutFeatureFlagBody(featureFlagValue)
|
||||
)
|
||||
} throws nonRetryableException
|
||||
remoteDataSource.update(userId, featureId, featureFlagValue)
|
||||
} throws ApiException(ApiResult.Error.Parse(SerializationException()))
|
||||
|
||||
// when
|
||||
val actual = worker.doWork()
|
||||
|
||||
// then
|
||||
coVerify { localDataSource.updateValue(userId, featureId, featureFlagValue.not()) }
|
||||
assertEquals(androidx.work.ListenableWorker.Result.failure(), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `worker rollbacks local feature flag value when API calls fails with non retryable exception`() = runTest {
|
||||
// given
|
||||
coEvery {
|
||||
featuresApi.putFeatureFlag(
|
||||
featureId.id,
|
||||
PutFeatureFlagBody(featureFlagValue)
|
||||
)
|
||||
} throws nonRetryableException
|
||||
|
||||
// when
|
||||
worker.doWork()
|
||||
|
||||
// then
|
||||
coVerify { localDataSource.updateValue(userId, featureId, featureFlagValue.not()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,6 @@ import me.proton.core.featureflag.data.remote.FeatureFlagRemoteDataSourceImpl
|
|||
import me.proton.core.featureflag.data.remote.FeaturesApi
|
||||
import me.proton.core.featureflag.data.remote.response.GetFeaturesResponse
|
||||
import me.proton.core.featureflag.data.remote.response.GetUnleashTogglesResponse
|
||||
import me.proton.core.featureflag.data.remote.worker.FeatureFlagWorkerManager
|
||||
import me.proton.core.featureflag.data.testdata.FeatureFlagTestData
|
||||
import me.proton.core.featureflag.data.testdata.FeatureFlagTestData.disabledFeature
|
||||
import me.proton.core.featureflag.data.testdata.FeatureFlagTestData.disabledFeatureApiResponse
|
||||
|
@ -59,6 +58,7 @@ import me.proton.core.featureflag.data.testdata.FeatureFlagTestData.featureId
|
|||
import me.proton.core.featureflag.data.testdata.FeatureFlagTestData.featureId1
|
||||
import me.proton.core.featureflag.data.testdata.SessionIdTestData
|
||||
import me.proton.core.featureflag.data.testdata.UserIdTestData.userId
|
||||
import me.proton.core.featureflag.domain.FeatureFlagWorkerManager
|
||||
import me.proton.core.featureflag.domain.entity.FeatureFlag
|
||||
import me.proton.core.featureflag.domain.entity.FeatureId
|
||||
import me.proton.core.featureflag.domain.entity.Scope
|
||||
|
@ -115,7 +115,6 @@ class FeatureFlagRepositoryImplTest : CoroutinesTest by UnconfinedCoroutinesTest
|
|||
)
|
||||
} returns TestApiManager(featuresApi)
|
||||
}
|
||||
private val workManager = mockk<WorkManager>(relaxed = true)
|
||||
private val workerManager = mockk<FeatureFlagWorkerManager>(relaxed = true)
|
||||
|
||||
private val observabilityManager = mockk<ObservabilityManager>(relaxed = true)
|
||||
|
@ -131,7 +130,7 @@ class FeatureFlagRepositoryImplTest : CoroutinesTest by UnconfinedCoroutinesTest
|
|||
apiProvider = ApiProvider(apiManagerFactory, sessionProvider, coroutinesRule.dispatchers)
|
||||
featureFlagContextProvider = TestFeatureFlagContextProvider()
|
||||
local = spyk(FeatureFlagLocalDataSourceImpl(database))
|
||||
remote = spyk(FeatureFlagRemoteDataSourceImpl(apiProvider, workManager, Optional.of(featureFlagContextProvider)))
|
||||
remote = spyk(FeatureFlagRemoteDataSourceImpl(apiProvider, Optional.of(featureFlagContextProvider)))
|
||||
repository = FeatureFlagRepositoryImpl(
|
||||
localDataSource = local,
|
||||
remoteDataSource = remote,
|
||||
|
@ -372,7 +371,7 @@ class FeatureFlagRepositoryImplTest : CoroutinesTest by UnconfinedCoroutinesTest
|
|||
val featureIds = setOf(featureId, featureId1)
|
||||
repository.prefetch(userId, featureIds)
|
||||
|
||||
coVerify { remote.prefetch(userId, featureIds) }
|
||||
coVerify { workerManager.prefetch(userId, featureIds) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -385,7 +384,7 @@ class FeatureFlagRepositoryImplTest : CoroutinesTest by UnconfinedCoroutinesTest
|
|||
|
||||
// Then
|
||||
coVerify { local.upsert(listOf(featureFlag)) }
|
||||
coVerify { remote.update(featureFlag) }
|
||||
coVerify { workerManager.update(featureFlag) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -25,6 +25,14 @@ public final class me/proton/core/featureflag/domain/FeatureFlagManager$DefaultI
|
|||
public static synthetic fun refreshAll$default (Lme/proton/core/featureflag/domain/FeatureFlagManager;Lme/proton/core/domain/entity/UserId;ILjava/lang/Object;)V
|
||||
}
|
||||
|
||||
public abstract interface class me/proton/core/featureflag/domain/FeatureFlagWorkerManager {
|
||||
public abstract fun cancel (Lme/proton/core/domain/entity/UserId;)V
|
||||
public abstract fun enqueueOneTime (Lme/proton/core/domain/entity/UserId;)V
|
||||
public abstract fun enqueuePeriodic (Lme/proton/core/domain/entity/UserId;Z)V
|
||||
public abstract fun prefetch (Lme/proton/core/domain/entity/UserId;Ljava/util/Set;)V
|
||||
public abstract fun update (Lme/proton/core/featureflag/domain/entity/FeatureFlag;)V
|
||||
}
|
||||
|
||||
public abstract interface class me/proton/core/featureflag/domain/IsFeatureFlagEnabled {
|
||||
public abstract fun invoke (Lme/proton/core/domain/entity/UserId;)Z
|
||||
public abstract fun isLocalEnabled ()Z
|
||||
|
@ -99,8 +107,7 @@ public abstract interface class me/proton/core/featureflag/domain/repository/Fea
|
|||
public abstract interface class me/proton/core/featureflag/domain/repository/FeatureFlagRemoteDataSource {
|
||||
public abstract fun get (Lme/proton/core/domain/entity/UserId;Ljava/util/Set;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun getAll (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun prefetch (Lme/proton/core/domain/entity/UserId;Ljava/util/Set;)V
|
||||
public abstract fun update (Lme/proton/core/featureflag/domain/entity/FeatureFlag;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun update (Lme/proton/core/domain/entity/UserId;Lme/proton/core/featureflag/domain/entity/FeatureId;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public abstract interface class me/proton/core/featureflag/domain/repository/FeatureFlagRepository {
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.featureflag.domain
|
||||
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.featureflag.domain.entity.FeatureFlag
|
||||
import me.proton.core.featureflag.domain.entity.FeatureId
|
||||
|
||||
public interface FeatureFlagWorkerManager {
|
||||
public fun enqueueOneTime(userId: UserId?)
|
||||
public fun enqueuePeriodic(userId: UserId?, immediately: Boolean)
|
||||
public fun cancel(userId: UserId?)
|
||||
public fun update(featureFlag: FeatureFlag)
|
||||
public fun prefetch(userId: UserId?, featureIds: Set<FeatureId>)
|
||||
}
|
|
@ -25,6 +25,5 @@ import me.proton.core.featureflag.domain.entity.FeatureId
|
|||
public interface FeatureFlagRemoteDataSource {
|
||||
public suspend fun getAll(userId: UserId?): List<FeatureFlag>
|
||||
public suspend fun get(userId: UserId?, ids: Set<FeatureId>): List<FeatureFlag>
|
||||
public suspend fun update(featureFlag: FeatureFlag)
|
||||
public fun prefetch(userId: UserId?, featureIds: Set<FeatureId>)
|
||||
public suspend fun update(userId: UserId?, featureId: FeatureId, enabled: Boolean)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue