mirror of
https://github.com/bitwarden/android.git
synced 2026-05-26 07:57:09 -05:00
[PM-37814] feat: Add debug flag to disable self-host premium check (#6954)
This commit is contained in:
@@ -13,6 +13,7 @@ import com.x8bit.bitwarden.data.billing.repository.BillingRepositoryImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
@@ -57,6 +58,7 @@ object BillingModule {
|
||||
settingsDiskSource: SettingsDiskSource,
|
||||
vaultRepository: VaultRepository,
|
||||
featureFlagManager: FeatureFlagManager,
|
||||
environmentRepository: EnvironmentRepository,
|
||||
pushManager: PushManager,
|
||||
clock: Clock,
|
||||
dispatcherManager: DispatcherManager,
|
||||
@@ -66,6 +68,7 @@ object BillingModule {
|
||||
settingsDiskSource = settingsDiskSource,
|
||||
vaultRepository = vaultRepository,
|
||||
featureFlagManager = featureFlagManager,
|
||||
environmentRepository = environmentRepository,
|
||||
pushManager = pushManager,
|
||||
clock = clock,
|
||||
dispatcherManager = dispatcherManager,
|
||||
|
||||
@@ -38,6 +38,18 @@ interface PremiumStateManager {
|
||||
*/
|
||||
val subscriptionStatusStateFlow: StateFlow<SubscriptionStatusState>
|
||||
|
||||
/**
|
||||
* Emits whether the current state should be treated as self-hosted for premium upgrade
|
||||
* gating. Reactive equivalent of [isSelfHosted].
|
||||
*/
|
||||
val isSelfHostedFlow: StateFlow<Boolean>
|
||||
|
||||
/**
|
||||
* `true` when the current state should be treated as self-hosted for premium upgrade
|
||||
* gating, or `false` otherwise.
|
||||
*/
|
||||
val isSelfHosted: Boolean
|
||||
|
||||
/**
|
||||
* Returns `true` when the in-app upgrade flow is available, or `false` otherwise.
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.billing.manager
|
||||
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
import com.bitwarden.core.data.manager.model.FlagKey
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.data.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.activeUserIdChangesFlow
|
||||
@@ -13,6 +14,7 @@ import com.x8bit.bitwarden.data.billing.repository.model.SubscriptionStatusState
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.util.isActive
|
||||
import com.x8bit.bitwarden.data.platform.util.scanPairs
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
@@ -47,6 +49,7 @@ class PremiumStateManagerImpl(
|
||||
private val settingsDiskSource: SettingsDiskSource,
|
||||
vaultRepository: VaultRepository,
|
||||
private val featureFlagManager: FeatureFlagManager,
|
||||
private val environmentRepository: EnvironmentRepository,
|
||||
pushManager: PushManager,
|
||||
private val clock: Clock,
|
||||
dispatcherManager: DispatcherManager,
|
||||
@@ -140,6 +143,20 @@ class PremiumStateManagerImpl(
|
||||
initialValue = false,
|
||||
)
|
||||
|
||||
override val isSelfHostedFlow: StateFlow<Boolean> =
|
||||
combine(
|
||||
environmentRepository.environmentStateFlow,
|
||||
featureFlagManager.getFeatureFlagFlow(FlagKey.DebugDisableSelfHostPremiumCheck),
|
||||
) { environment, isDebugBypassEnabled ->
|
||||
environment is Environment.SelfHosted && !isDebugBypassEnabled
|
||||
}
|
||||
.stateIn(
|
||||
scope = unconfinedScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = environmentRepository.environment is Environment.SelfHosted &&
|
||||
!featureFlagManager.getFeatureFlag(FlagKey.DebugDisableSelfHostPremiumCheck),
|
||||
)
|
||||
|
||||
/**
|
||||
* Eligibility is keyed on the user holding personal Premium (or being eligible to purchase
|
||||
* it). Organization-granted Premium does not surface the Plan row, since the user has no
|
||||
@@ -242,6 +259,8 @@ class PremiumStateManagerImpl(
|
||||
.launchIn(unconfinedScope)
|
||||
}
|
||||
|
||||
override val isSelfHosted: Boolean get() = isSelfHostedFlow.value
|
||||
|
||||
override fun isInAppUpgradeAvailable(): Boolean =
|
||||
billingRepository.isInAppBillingSupportedFlow.value &&
|
||||
featureFlagManager.getFeatureFlag(FlagKey.MobilePremiumUpgrade)
|
||||
|
||||
@@ -6,7 +6,6 @@ import androidx.annotation.StringRes
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.core.data.util.toFormattedDateStyle
|
||||
import com.bitwarden.data.repository.model.Environment
|
||||
import com.bitwarden.data.repository.util.baseWebVaultUrlOrDefault
|
||||
import com.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.bitwarden.ui.platform.manager.intent.model.AuthTabData
|
||||
@@ -82,7 +81,7 @@ class PlanViewModel @Inject constructor(
|
||||
?.isPremium == true
|
||||
val showsPremiumView = isPremium ||
|
||||
premiumStateManager.subscriptionStatusStateFlow.value.isPremiumViewEligible()
|
||||
val isSelfHosted = environmentRepository.environment is Environment.SelfHosted
|
||||
val isSelfHosted = premiumStateManager.isSelfHosted
|
||||
PlanState(
|
||||
planMode = planMode,
|
||||
viewState = when {
|
||||
|
||||
@@ -4,7 +4,6 @@ import androidx.annotation.DrawableRes
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.data.repository.model.Environment
|
||||
import com.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.bitwarden.ui.platform.base.DeferredBackgroundEvent
|
||||
import com.bitwarden.ui.platform.resource.BitwardenDrawable
|
||||
@@ -16,7 +15,6 @@ import com.x8bit.bitwarden.data.billing.manager.UPGRADED_TO_PREMIUM_LEARN_MORE_U
|
||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -34,7 +32,6 @@ import javax.inject.Inject
|
||||
class SettingsViewModel @Inject constructor(
|
||||
specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
firstTimeActionManager: FirstTimeActionManager,
|
||||
environmentRepository: EnvironmentRepository,
|
||||
private val premiumStateManager: PremiumStateManager,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<SettingsState, SettingsEvent, SettingsAction>(
|
||||
@@ -44,7 +41,7 @@ class SettingsViewModel @Inject constructor(
|
||||
autoFillCount = firstTimeActionManager.allAutofillSettingsBadgeCountFlow.value,
|
||||
vaultCount = firstTimeActionManager.allVaultSettingsBadgeCountFlow.value,
|
||||
isPlanRowEligible = premiumStateManager.isPlanRowEligibleFlow.value,
|
||||
isSelfHosted = environmentRepository.environment is Environment.SelfHosted,
|
||||
isSelfHosted = premiumStateManager.isSelfHostedFlow.value,
|
||||
isUpgradedToPremiumCardEligible = premiumStateManager
|
||||
.isUpgradedToPremiumCardEligibleFlow
|
||||
.value,
|
||||
@@ -80,13 +77,9 @@ class SettingsViewModel @Inject constructor(
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
environmentRepository
|
||||
.environmentStateFlow
|
||||
.map {
|
||||
SettingsAction.Internal.EnvironmentReceive(
|
||||
isSelfHosted = it is Environment.SelfHosted,
|
||||
)
|
||||
}
|
||||
premiumStateManager
|
||||
.isSelfHostedFlow
|
||||
.map { SettingsAction.Internal.SelfHostedStatusReceive(isSelfHosted = it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
@@ -117,13 +110,13 @@ class SettingsViewModel @Inject constructor(
|
||||
handleUpgradedToPremiumCardEligibilityReceive(action)
|
||||
}
|
||||
|
||||
is SettingsAction.Internal.EnvironmentReceive -> {
|
||||
handleEnvironmentReceive(action)
|
||||
is SettingsAction.Internal.SelfHostedStatusReceive -> {
|
||||
handleSelfHostedStatusReceive(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleEnvironmentReceive(
|
||||
action: SettingsAction.Internal.EnvironmentReceive,
|
||||
private fun handleSelfHostedStatusReceive(
|
||||
action: SettingsAction.Internal.SelfHostedStatusReceive,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(isSelfHosted = action.isSelfHosted)
|
||||
@@ -364,9 +357,10 @@ sealed class SettingsAction {
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the environment has been updated.
|
||||
* Indicates that the effective self-hosted status for premium gating has been updated —
|
||||
* driven by environment changes or by the debug-only self-host-bypass flag.
|
||||
*/
|
||||
data class EnvironmentReceive(
|
||||
data class SelfHostedStatusReceive(
|
||||
val isSelfHosted: Boolean,
|
||||
) : Internal()
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.bitwarden.core.data.manager.dispatcher.FakeDispatcherManager
|
||||
import com.bitwarden.core.data.manager.model.FlagKey
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.data.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.bitwarden.data.repository.model.Environment
|
||||
import com.bitwarden.vault.DecryptCipherListResult
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
@@ -19,6 +20,7 @@ import com.x8bit.bitwarden.data.platform.datasource.disk.util.FakeSettingsDiskSo
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.PremiumStatusChangedData
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherListView
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
@@ -66,6 +68,7 @@ class PremiumStateManagerTest {
|
||||
}
|
||||
|
||||
private val mutableMobilePremiumUpgradeFlagFlow = MutableStateFlow(true)
|
||||
private val mutableDebugDisableSelfHostPremiumCheckFlagFlow = MutableStateFlow(false)
|
||||
private val featureFlagManager: FeatureFlagManager = mockk {
|
||||
every {
|
||||
getFeatureFlagFlow(FlagKey.MobilePremiumUpgrade)
|
||||
@@ -73,8 +76,16 @@ class PremiumStateManagerTest {
|
||||
every {
|
||||
getFeatureFlag(FlagKey.MobilePremiumUpgrade)
|
||||
} answers { mutableMobilePremiumUpgradeFlagFlow.value }
|
||||
every {
|
||||
getFeatureFlag(FlagKey.DebugDisableSelfHostPremiumCheck)
|
||||
} answers { mutableDebugDisableSelfHostPremiumCheckFlagFlow.value }
|
||||
every {
|
||||
getFeatureFlagFlow(FlagKey.DebugDisableSelfHostPremiumCheck)
|
||||
} returns mutableDebugDisableSelfHostPremiumCheckFlagFlow
|
||||
}
|
||||
|
||||
private val fakeEnvironmentRepository = FakeEnvironmentRepository()
|
||||
|
||||
private val dispatcherManager = FakeDispatcherManager()
|
||||
|
||||
private val mutablePremiumStatusChangedFlow =
|
||||
@@ -89,6 +100,7 @@ class PremiumStateManagerTest {
|
||||
settingsDiskSource = fakeSettingsDiskSource,
|
||||
vaultRepository = vaultRepository,
|
||||
featureFlagManager = featureFlagManager,
|
||||
environmentRepository = fakeEnvironmentRepository,
|
||||
pushManager = pushManager,
|
||||
clock = fixedClock,
|
||||
dispatcherManager = dispatcherManager,
|
||||
@@ -365,6 +377,75 @@ class PremiumStateManagerTest {
|
||||
assertFalse(manager.isInAppUpgradeAvailable())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isSelfHosted should return false on cloud environment regardless of flag`() {
|
||||
fakeEnvironmentRepository.environment = Environment.Us
|
||||
val manager = createManager()
|
||||
mutableDebugDisableSelfHostPremiumCheckFlagFlow.value = false
|
||||
assertFalse(manager.isSelfHosted)
|
||||
mutableDebugDisableSelfHostPremiumCheckFlagFlow.value = true
|
||||
assertFalse(manager.isSelfHosted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isSelfHosted should return true on self-hosted environment when flag is disabled`() {
|
||||
fakeEnvironmentRepository.environment = Environment.SelfHosted(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
)
|
||||
mutableDebugDisableSelfHostPremiumCheckFlagFlow.value = false
|
||||
val manager = createManager()
|
||||
assertTrue(manager.isSelfHosted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isSelfHosted should return false on self-hosted environment when flag is enabled`() {
|
||||
fakeEnvironmentRepository.environment = Environment.SelfHosted(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
)
|
||||
mutableDebugDisableSelfHostPremiumCheckFlagFlow.value = true
|
||||
val manager = createManager()
|
||||
assertFalse(manager.isSelfHosted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isSelfHostedFlow should emit false on cloud environment regardless of flag`() = runTest {
|
||||
fakeEnvironmentRepository.environment = Environment.Us
|
||||
val manager = createManager()
|
||||
manager.isSelfHostedFlow.test {
|
||||
assertFalse(awaitItem())
|
||||
mutableDebugDisableSelfHostPremiumCheckFlagFlow.value = true
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isSelfHostedFlow should emit true on self-hosted environment when flag is disabled`() =
|
||||
runTest {
|
||||
fakeEnvironmentRepository.environment = Environment.SelfHosted(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
)
|
||||
val manager = createManager()
|
||||
manager.isSelfHostedFlow.test {
|
||||
assertTrue(awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isSelfHostedFlow should re-emit when debug-disable flag toggles on self-hosted env`() =
|
||||
runTest {
|
||||
fakeEnvironmentRepository.environment = Environment.SelfHosted(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
)
|
||||
val manager = createManager()
|
||||
manager.isSelfHostedFlow.test {
|
||||
assertTrue(awaitItem())
|
||||
mutableDebugDisableSelfHostPremiumCheckFlagFlow.value = true
|
||||
assertFalse(awaitItem())
|
||||
mutableDebugDisableSelfHostPremiumCheckFlagFlow.value = false
|
||||
assertTrue(awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dismissPremiumUpgradeBanner should store dismissed state for active user`() {
|
||||
val manager = createManager()
|
||||
|
||||
@@ -67,8 +67,10 @@ class PlanViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
private val mutableSubscriptionStatusStateFlow =
|
||||
MutableStateFlow<SubscriptionStatusState>(SubscriptionStatusState.NoSubscription)
|
||||
private var mockIsSelfHosted = false
|
||||
private val mockPremiumStateManager: PremiumStateManager = mockk {
|
||||
every { subscriptionStatusStateFlow } returns mutableSubscriptionStatusStateFlow
|
||||
every { isSelfHosted } answers { mockIsSelfHosted }
|
||||
}
|
||||
private val mutableEnvironmentFlow = MutableStateFlow<Environment>(Environment.Us)
|
||||
private val mockEnvironmentRepository: EnvironmentRepository = mockk {
|
||||
@@ -612,6 +614,7 @@ class PlanViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `initial state on self-hosted should be Free SelfHosted ViewState`() = runTest {
|
||||
mockIsSelfHosted = true
|
||||
mutableEnvironmentFlow.value = Environment.SelfHosted(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
)
|
||||
@@ -633,6 +636,7 @@ class PlanViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `initial state on self-hosted should not fetch pricing`() = runTest {
|
||||
mockIsSelfHosted = true
|
||||
mutableEnvironmentFlow.value = Environment.SelfHosted(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
)
|
||||
@@ -643,6 +647,36 @@ class PlanViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `self-hosted with debug disable flag enabled should show Free Cloud and fetch pricing`() =
|
||||
runTest {
|
||||
// The PremiumStateManager helper reports false when the debug-disable flag is on,
|
||||
// so the view model treats the self-hosted env as cloud for premium-upgrade purposes.
|
||||
mockIsSelfHosted = false
|
||||
mutableEnvironmentFlow.value = Environment.SelfHosted(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
PlanState(
|
||||
planMode = PlanMode.Modal,
|
||||
viewState = PlanState.ViewState.Free.Cloud(
|
||||
rate = "$1.67",
|
||||
checkoutUrl = null,
|
||||
isAwaitingPremiumStatus = false,
|
||||
),
|
||||
dialogState = null,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
coVerify(exactly = 1) {
|
||||
mockBillingRepository.getPremiumPlanPricing()
|
||||
}
|
||||
}
|
||||
|
||||
// endregion Self-hosted path
|
||||
|
||||
// region Pricing fetch
|
||||
|
||||
@@ -2,14 +2,11 @@ package com.x8bit.bitwarden.ui.platform.feature.settings
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.data.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.bitwarden.data.repository.model.Environment
|
||||
import com.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.data.billing.manager.PremiumStateManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
@@ -44,16 +41,13 @@ class SettingsViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private val mutablePlanRowEligibleFlow = MutableStateFlow(false)
|
||||
private val mutableUpgradedToPremiumCardEligibleFlow = MutableStateFlow(false)
|
||||
private val mutableIsSelfHostedFlow = MutableStateFlow(false)
|
||||
private val premiumStateManager: PremiumStateManager = mockk(relaxed = true) {
|
||||
every { isPlanRowEligibleFlow } returns mutablePlanRowEligibleFlow
|
||||
every {
|
||||
isUpgradedToPremiumCardEligibleFlow
|
||||
} returns mutableUpgradedToPremiumCardEligibleFlow
|
||||
}
|
||||
private val mutableEnvironmentFlow = MutableStateFlow<Environment>(Environment.Us)
|
||||
private val environmentRepository: EnvironmentRepository = mockk {
|
||||
every { environment } answers { mutableEnvironmentFlow.value }
|
||||
every { environmentStateFlow } returns mutableEnvironmentFlow
|
||||
every { isSelfHostedFlow } returns mutableIsSelfHostedFlow
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
@@ -332,11 +326,9 @@ class SettingsViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Plan row should be hidden when environment is self-hosted`() {
|
||||
fun `Plan row should be hidden when self-hosted status flow emits true`() {
|
||||
mutablePlanRowEligibleFlow.value = true
|
||||
mutableEnvironmentFlow.value = Environment.SelfHosted(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
)
|
||||
mutableIsSelfHostedFlow.value = true
|
||||
val viewModel = createViewModel()
|
||||
assertFalse(
|
||||
viewModel.stateFlow.value.settingRows
|
||||
@@ -345,7 +337,7 @@ class SettingsViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Plan row should update when environment changes to self-hosted`() = runTest {
|
||||
fun `Plan row should update when self-hosted status flow flips to true`() = runTest {
|
||||
mutablePlanRowEligibleFlow.value = true
|
||||
val viewModel = createViewModel()
|
||||
assertTrue(
|
||||
@@ -353,9 +345,7 @@ class SettingsViewModelTest : BaseViewModelTest() {
|
||||
.contains(Settings.PLAN),
|
||||
)
|
||||
|
||||
mutableEnvironmentFlow.value = Environment.SelfHosted(
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
)
|
||||
mutableIsSelfHostedFlow.value = true
|
||||
viewModel.stateFlow.test {
|
||||
assertFalse(
|
||||
awaitItem().settingRows.contains(Settings.PLAN),
|
||||
@@ -363,10 +353,29 @@ class SettingsViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Plan row should re-appear when self-hosted status flow flips back to false`() = runTest {
|
||||
// Simulates the debug-disable flag being toggled on while self-hosted: the flow emits
|
||||
// false even though the environment is still self-hosted.
|
||||
mutablePlanRowEligibleFlow.value = true
|
||||
mutableIsSelfHostedFlow.value = true
|
||||
val viewModel = createViewModel()
|
||||
assertFalse(
|
||||
viewModel.stateFlow.value.settingRows
|
||||
.contains(Settings.PLAN),
|
||||
)
|
||||
|
||||
mutableIsSelfHostedFlow.value = false
|
||||
viewModel.stateFlow.test {
|
||||
assertTrue(
|
||||
awaitItem().settingRows.contains(Settings.PLAN),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(isPreAuth: Boolean = false) = SettingsViewModel(
|
||||
firstTimeActionManager = firstTimeManager,
|
||||
specialCircumstanceManager = specialCircumstanceManager,
|
||||
environmentRepository = environmentRepository,
|
||||
premiumStateManager = premiumStateManager,
|
||||
savedStateHandle = SavedStateHandle().apply {
|
||||
every { toSettingsArgs() } returns SettingsArgs(isPreAuth = isPreAuth)
|
||||
|
||||
@@ -45,6 +45,7 @@ sealed class FlagKey<out T : Any> {
|
||||
V2EncryptionPassword,
|
||||
V2EncryptionTde,
|
||||
NewItemTypes,
|
||||
DebugDisableSelfHostPremiumCheck,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -180,6 +181,16 @@ sealed class FlagKey<out T : Any> {
|
||||
override val defaultValue: Boolean = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug-only flag that, when enabled, makes self-hosted environments behave as cloud
|
||||
* environments for premium-upgrade gating. Used by QA to test the premium upgrade flow
|
||||
* against internal self-hosted environments.
|
||||
*/
|
||||
data object DebugDisableSelfHostPremiumCheck : FlagKey<Boolean>() {
|
||||
override val keyName: String = "debug-disable-self-host-premium-check"
|
||||
override val defaultValue: Boolean = false
|
||||
}
|
||||
|
||||
//region Dummy keys for testing
|
||||
/**
|
||||
* Data object holding the key for a [Boolean] flag to be used in tests.
|
||||
|
||||
@@ -39,6 +39,7 @@ fun <T : Any> FlagKey<T>.ListItemContent(
|
||||
FlagKey.V2EncryptionPassword,
|
||||
FlagKey.V2EncryptionTde,
|
||||
FlagKey.NewItemTypes,
|
||||
FlagKey.DebugDisableSelfHostPremiumCheck,
|
||||
-> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
BooleanFlagItem(
|
||||
@@ -99,4 +100,7 @@ private fun <T : Any> FlagKey<T>.getDisplayLabel(): String = when (this) {
|
||||
FlagKey.V2EncryptionPassword -> stringResource(BitwardenString.v2_encryption_password)
|
||||
FlagKey.V2EncryptionTde -> stringResource(BitwardenString.v2_encryption_tde)
|
||||
FlagKey.NewItemTypes -> stringResource(BitwardenString.new_item_types)
|
||||
FlagKey.DebugDisableSelfHostPremiumCheck -> {
|
||||
stringResource(BitwardenString.debug_disable_self_host_premium_check)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
<string name="v2_encryption_password">V2 Encryption - Password</string>
|
||||
<string name="manage_devices">Manage devices</string>
|
||||
<string name="new_item_types">New Item Types</string>
|
||||
<string name="debug_disable_self_host_premium_check">Debug: Disable self-host premium check</string>
|
||||
|
||||
<!-- endregion Debug Menu -->
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user