Update definition of Environment.label (#349)

This commit is contained in:
Brian Yencho
2023-12-07 14:35:08 -06:00
committed by GitHub
parent 6498ab2ffb
commit 611d8f5711
18 changed files with 160 additions and 74 deletions

View File

@@ -1,11 +1,10 @@
package com.x8bit.bitwarden.data.platform.repository
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.data.platform.repository.util.toEnvironmentUrls
import com.x8bit.bitwarden.data.platform.repository.util.toEnvironmentUrlsOrDefault
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -57,10 +56,3 @@ class EnvironmentRepositoryImpl(
.launchIn(scope)
}
}
/**
* Converts a nullable [EnvironmentUrlDataJson] to an [Environment], where `null` values default to
* the US environment.
*/
private fun EnvironmentUrlDataJson?.toEnvironmentUrlsOrDefault(): Environment =
this?.toEnvironmentUrls() ?: Environment.Us

View File

@@ -1,9 +1,7 @@
package com.x8bit.bitwarden.data.platform.repository.model
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.data.platform.repository.util.labelOrBaseUrlHost
/**
* A higher-level wrapper around [EnvironmentUrlDataJson] that provides type-safety, enumerability,
@@ -21,9 +19,9 @@ sealed class Environment {
abstract val environmentUrlData: EnvironmentUrlDataJson
/**
* Helper for a returning a human-readable label from a [Type].
* A human-readable label for the environment based in some way on its base URL.
*/
val label: Text get() = type.label
abstract val label: String
/**
* The default US environment.
@@ -32,6 +30,8 @@ sealed class Environment {
override val type: Type get() = Type.US
override val environmentUrlData: EnvironmentUrlDataJson
get() = EnvironmentUrlDataJson.DEFAULT_US
override val label: String
get() = "bitwarden.com"
}
/**
@@ -41,6 +41,8 @@ sealed class Environment {
override val type: Type get() = Type.EU
override val environmentUrlData: EnvironmentUrlDataJson
get() = EnvironmentUrlDataJson.DEFAULT_EU
override val label: String
get() = "bitwarden.eu"
}
/**
@@ -50,15 +52,17 @@ sealed class Environment {
override val environmentUrlData: EnvironmentUrlDataJson,
) : Environment() {
override val type: Type get() = Type.SELF_HOSTED
override val label: String
get() = environmentUrlData.labelOrBaseUrlHost
}
/**
* A summary of the various types that can be enumerated over and which contains a
* human-readable [label].
*/
enum class Type(val label: Text) {
US(label = "bitwarden.com".asText()),
EU(label = "bitwarden.eu".asText()),
SELF_HOSTED(label = R.string.self_hosted.asText()),
enum class Type {
US,
EU,
SELF_HOSTED,
}
}

View File

@@ -12,3 +12,10 @@ fun EnvironmentUrlDataJson.toEnvironmentUrls(): Environment =
Environment.Eu.environmentUrlData -> Environment.Eu
else -> Environment.SelfHosted(environmentUrlData = this)
}
/**
* Converts a nullable [EnvironmentUrlDataJson] to an [Environment], where `null` values default to
* the US environment.
*/
fun EnvironmentUrlDataJson?.toEnvironmentUrlsOrDefault(): Environment =
this?.toEnvironmentUrls() ?: Environment.Us

View File

@@ -0,0 +1,24 @@
package com.x8bit.bitwarden.data.platform.repository.util
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import java.net.URI
/**
* Returns the appropriate pre-defined labels for environments matching the known US/EU values.
* Otherwise returns the host of the custom base URL.
*/
val EnvironmentUrlDataJson.labelOrBaseUrlHost: String
get() = when (this) {
EnvironmentUrlDataJson.DEFAULT_US -> Environment.Us.label
EnvironmentUrlDataJson.DEFAULT_EU -> Environment.Eu.label
else -> {
// Grab the domain
// Ex:
// - "https://www.abc.com/path-1/path-1" -> "www.abc.com"
URI
.create(this.base)
.host
.orEmpty()
}
}

View File

@@ -61,6 +61,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
import com.x8bit.bitwarden.ui.platform.util.displayLabel
import kotlinx.collections.immutable.toImmutableList
/**
@@ -363,7 +364,7 @@ private fun EnvironmentSelector(
modifier = Modifier.padding(end = 12.dp),
)
Text(
text = selectedOption.label(),
text = selectedOption.displayLabel(),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(end = 8.dp),
@@ -382,7 +383,7 @@ private fun EnvironmentSelector(
) {
options.forEach {
BitwardenSelectionRow(
text = it.label,
text = it.displayLabel,
onClick = {
onOptionSelected.invoke(it)
shouldShowDialog = false

View File

@@ -241,7 +241,7 @@ private fun LoginScreenContent(
text = stringResource(
id = R.string.logging_in_as_x_on_y,
state.emailAddress,
state.environmentLabel(),
state.environmentLabel,
),
textAlign = TextAlign.Start,
style = MaterialTheme.typography.bodyMedium,

View File

@@ -13,12 +13,10 @@ import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
import com.x8bit.bitwarden.ui.platform.util.labelOrBaseUrlHost
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
@@ -44,7 +42,7 @@ class LoginViewModel @Inject constructor(
emailAddress = LoginArgs(savedStateHandle).emailAddress,
isLoginButtonEnabled = true,
passwordInput = "",
environmentLabel = environmentRepository.environment.labelOrBaseUrlHost,
environmentLabel = environmentRepository.environment.label,
loadingDialogState = LoadingDialogState.Hidden,
errorDialogState = BasicDialogState.Hidden,
captchaToken = LoginArgs(savedStateHandle).captchaToken,
@@ -205,7 +203,7 @@ data class LoginState(
val passwordInput: String,
val emailAddress: String,
val captchaToken: String?,
val environmentLabel: Text,
val environmentLabel: String,
val isLoginButtonEnabled: Boolean,
val loadingDialogState: LoadingDialogState,
val errorDialogState: BasicDialogState,

View File

@@ -168,7 +168,7 @@ fun VaultUnlockScreen(
text = stringResource(
id = R.string.logged_in_as_on,
state.email,
state.environmentUrl(),
state.environmentUrl,
),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,

View File

@@ -15,7 +15,6 @@ import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.base.util.hexToColor
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
import com.x8bit.bitwarden.ui.platform.util.labelOrBaseUrlHost
import com.x8bit.bitwarden.ui.vault.feature.vault.util.initials
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toActiveAccountSummary
@@ -49,7 +48,7 @@ class VaultUnlockViewModel @Inject constructor(
initials = activeAccountSummary.initials,
email = activeAccountSummary.email,
dialog = null,
environmentUrl = environmentRepo.environment.labelOrBaseUrlHost,
environmentUrl = environmentRepo.environment.label,
passwordInput = "",
)
},
@@ -62,7 +61,7 @@ class VaultUnlockViewModel @Inject constructor(
.environmentStateFlow
.onEach { environment ->
mutableStateFlow.update {
it.copy(environmentUrl = environment.labelOrBaseUrlHost)
it.copy(environmentUrl = environment.label)
}
}
.launchIn(viewModelScope)
@@ -186,7 +185,7 @@ data class VaultUnlockState(
private val avatarColorString: String,
val initials: String,
val email: String,
val environmentUrl: Text,
val environmentUrl: String,
val dialog: VaultUnlockDialog?,
val passwordInput: String,
) : Parcelable {

View File

@@ -1,26 +1,16 @@
package com.x8bit.bitwarden.ui.platform.util
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
import java.net.URI
/**
* Returns the [Environment.label] for non-custom values. Otherwise returns the host of the
* custom base URL.
* Returns a human-readable display label for the given [Environment.Type].
*/
val Environment.labelOrBaseUrlHost: Text
val Environment.Type.displayLabel: Text
get() = when (this) {
is Environment.Us -> this.label
is Environment.Eu -> this.label
is Environment.SelfHosted -> {
// Grab the domain
// Ex:
// - "https://www.abc.com/path-1/path-1" -> "www.abc.com"
URI
.create(this.environmentUrlData.base)
.host
.orEmpty()
.asText()
}
Environment.Type.US -> Environment.Us.label.asText()
Environment.Type.EU -> Environment.Eu.label.asText()
Environment.Type.SELF_HOSTED -> R.string.self_hosted.asText()
}

View File

@@ -40,4 +40,47 @@ class EnvironmentExtensionsTest {
environmentUrlData.toEnvironmentUrls(),
)
}
@Test
fun `toEnvironmentUrlsOrDefault should correctly convert US urls to the expected type`() {
assertEquals(
Environment.Us,
EnvironmentUrlDataJson.DEFAULT_US.toEnvironmentUrlsOrDefault(),
)
}
@Test
fun `toEnvironmentUrlsOrDefault should correctly convert EU urls to the expected type`() {
assertEquals(
Environment.Eu,
EnvironmentUrlDataJson.DEFAULT_EU.toEnvironmentUrlsOrDefault(),
)
}
@Test
fun `toEnvironmentUrlsOrDefault should correctly convert custom urls to the expected type`() {
val environmentUrlData = EnvironmentUrlDataJson(
base = "base",
api = "api",
identity = "identity",
icon = "icon",
notifications = "notifications",
webVault = "webVault",
events = "events",
)
assertEquals(
Environment.SelfHosted(
environmentUrlData = environmentUrlData,
),
environmentUrlData.toEnvironmentUrlsOrDefault(),
)
}
@Test
fun `toEnvironmentUrlsOrDefault should convert null types to US values`() {
assertEquals(
Environment.Us,
(null as EnvironmentUrlDataJson?).toEnvironmentUrlsOrDefault(),
)
}
}

View File

@@ -0,0 +1,36 @@
package com.x8bit.bitwarden.data.platform.repository.util
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class EnvironmentUrlsDataJsonExtensionsTest {
@Test
fun `labelOrBaseUrlHost should correctly convert US environment to the correct label`() {
val environment = EnvironmentUrlDataJson.DEFAULT_US
assertEquals(
Environment.Us.label,
environment.labelOrBaseUrlHost,
)
}
@Test
fun `labelOrBaseUrlHost should correctly convert EU environment to the correct label`() {
val environment = EnvironmentUrlDataJson.DEFAULT_EU
assertEquals(
Environment.Eu.label,
environment.labelOrBaseUrlHost,
)
}
@Suppress("MaxLineLength")
@Test
fun `labelOrBaseUrlHost should correctly convert self hosted environment to the correct label`() {
val environment = EnvironmentUrlDataJson(base = "https://vault.qa.bitwarden.pw")
assertEquals(
"vault.qa.bitwarden.pw",
environment.labelOrBaseUrlHost,
)
}
}

View File

@@ -210,12 +210,12 @@ class LandingScreenTest : BaseComposeTest() {
// Clicking to open dialog
composeTestRule
.onNodeWithText(Environment.Us.label.toString(resources))
.onNodeWithText(Environment.Us.label)
.performClick()
// Clicking item on dialog
composeTestRule
.onNodeWithText(selectedEnvironment.label.toString(resources))
.onNodeWithText(selectedEnvironment.label)
.assert(hasAnyAncestor(isDialog()))
.performClick()

View File

@@ -15,7 +15,6 @@ import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performTextInput
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
@@ -186,7 +185,7 @@ private val DEFAULT_STATE =
captchaToken = null,
isLoginButtonEnabled = false,
passwordInput = "",
environmentLabel = "".asText(),
environmentLabel = "",
loadingDialogState = LoadingDialogState.Hidden,
errorDialogState = BasicDialogState.Hidden,
accountSummaries = emptyList(),

View File

@@ -75,7 +75,7 @@ class LoginViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.test {
assertEquals(
DEFAULT_STATE.copy(
environmentLabel = "".asText(),
environmentLabel = "",
),
awaitItem(),
)
@@ -94,7 +94,7 @@ class LoginViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.test {
assertEquals(
DEFAULT_STATE.copy(
environmentLabel = "abc.com".asText(),
environmentLabel = "abc.com",
),
awaitItem(),
)
@@ -323,7 +323,7 @@ class LoginViewModelTest : BaseViewModelTest() {
emailAddress = "test@gmail.com",
passwordInput = "",
isLoginButtonEnabled = true,
environmentLabel = Environment.Us.type.label,
environmentLabel = Environment.Us.label,
loadingDialogState = LoadingDialogState.Hidden,
errorDialogState = BasicDialogState.Hidden,
captchaToken = null,

View File

@@ -14,7 +14,6 @@ import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performTextInput
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
import io.mockk.every
import io.mockk.mockk
@@ -154,7 +153,7 @@ class VaultUnlockScreenTest : BaseComposeTest() {
val textAfterUpdate = "Logged in as ${DEFAULT_STATE.email} on $newEnvironmentUrl."
composeTestRule.onNodeWithText(textBeforeUpdate).assertExists()
composeTestRule.onNodeWithText(textAfterUpdate).assertDoesNotExist()
mutableStateFlow.update { it.copy(environmentUrl = newEnvironmentUrl.asText()) }
mutableStateFlow.update { it.copy(environmentUrl = newEnvironmentUrl) }
composeTestRule.onNodeWithText(textBeforeUpdate).assertDoesNotExist()
composeTestRule.onNodeWithText(textAfterUpdate).assertExists()
}
@@ -215,7 +214,7 @@ private val DEFAULT_STATE: VaultUnlockState = VaultUnlockState(
avatarColorString = "0000FF",
dialog = null,
email = "bit@bitwarden.com",
environmentUrl = DEFAULT_ENVIRONMENT_URL.asText(),
environmentUrl = DEFAULT_ENVIRONMENT_URL,
initials = "AU",
passwordInput = "",
)

View File

@@ -60,10 +60,10 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel()
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
environmentRepository.environment = Environment.SelfHosted(
environmentUrlData = EnvironmentUrlDataJson(base = "https://vault.bitwarden.eu"),
environmentUrlData = EnvironmentUrlDataJson(base = "https://vault.qa.bitwarden.pw"),
)
assertEquals(
DEFAULT_STATE.copy(environmentUrl = "vault.bitwarden.eu".asText()),
DEFAULT_STATE.copy(environmentUrl = "vault.qa.bitwarden.pw"),
viewModel.stateFlow.value,
)
}

View File

@@ -1,6 +1,6 @@
package com.x8bit.bitwarden.ui.platform.util
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.ui.platform.base.util.asText
import org.junit.jupiter.api.Assertions.assertEquals
@@ -8,32 +8,26 @@ import org.junit.jupiter.api.Test
class EnvironmentExtensionsTest {
@Test
fun `labelOrBaseUrlHost should correctly convert US environment to the correct label`() {
val environment = Environment.Us
fun `displayLabel for US type should return the correct value`() {
assertEquals(
environment.label,
environment.labelOrBaseUrlHost,
"bitwarden.com".asText(),
Environment.Type.US.displayLabel,
)
}
@Test
fun `labelOrBaseUrlHost should correctly convert EU environment to the correct label`() {
val environment = Environment.Eu
fun `displayLabel for EU type should return the correct value`() {
assertEquals(
environment.label,
environment.labelOrBaseUrlHost,
"bitwarden.eu".asText(),
Environment.Type.EU.displayLabel,
)
}
@Suppress("MaxLineLength")
@Test
fun `labelOrBaseUrlHost should correctly convert self hosted environment to the correct label`() {
val environment = Environment.SelfHosted(
environmentUrlData = EnvironmentUrlDataJson(base = "https://vault.bitwarden.com"),
)
fun `displayLabel for SELF_HOSTED type should return the correct value`() {
assertEquals(
"vault.bitwarden.com".asText(),
environment.labelOrBaseUrlHost,
R.string.self_hosted.asText(),
Environment.Type.SELF_HOSTED.displayLabel,
)
}
}