mirror of
https://github.com/bitwarden/android.git
synced 2026-10-23 11:33:22 -05:00
BIT-769: Move to organization functionality (#670)
This commit is contained in:
1 parent
413677852b
commit
9ba6474c37
15 files changed
+1008
-188
No files matched your search
+22
@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.vault.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.api.CiphersApi
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipher
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipherJsonRequest
|
||||
@@ -71,6 +72,27 @@ class CiphersServiceTest : BaseServiceTest() {
|
||||
val result = ciphersService.deleteCipher(cipherId = cipherId)
|
||||
assertEquals(Unit, result.getOrThrow())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `shareCipher should execute the share cipher API`() = runTest {
|
||||
server.enqueue(
|
||||
MockResponse()
|
||||
.setResponseCode(200)
|
||||
.setBody(CREATE_UPDATE_CIPHER_SUCCESS_JSON),
|
||||
)
|
||||
|
||||
val result = ciphersService.shareCipher(
|
||||
cipherId = "mockId-1",
|
||||
body = ShareCipherJsonRequest(
|
||||
cipher = createMockCipherJsonRequest(number = 1),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
createMockCipher(number = 1),
|
||||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val CREATE_UPDATE_CIPHER_SUCCESS_JSON = """
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFl
|
||||
import com.x8bit.bitwarden.data.platform.util.asFailure
|
||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendFileResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendTypeJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
@@ -58,6 +59,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.SendData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
@@ -2000,6 +2002,111 @@ class VaultRepositoryTest {
|
||||
assertEquals(DeleteSendResult.Success, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `shareCipher with cipherService shareCipher success should return ShareCipherResultSuccess`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
coEvery {
|
||||
vaultSdkSource.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
coEvery {
|
||||
ciphersService.shareCipher(
|
||||
cipherId = "mockId-1",
|
||||
body = ShareCipherJsonRequest(
|
||||
cipher = createMockCipherJsonRequest(number = 1),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
),
|
||||
)
|
||||
} returns createMockCipher(number = 1).asSuccess()
|
||||
coEvery { vaultDiskSource.saveCipher(userId, createMockCipher(number = 1)) } just runs
|
||||
|
||||
val result = vaultRepository.shareCipher(
|
||||
cipherId = "mockId-1",
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
ShareCipherResult.Success,
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `shareCipher with cipherService shareCipher failure should return ShareCipherResultError`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
coEvery {
|
||||
vaultSdkSource.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
coEvery {
|
||||
ciphersService.shareCipher(
|
||||
cipherId = "mockId-1",
|
||||
body = ShareCipherJsonRequest(
|
||||
cipher = createMockCipherJsonRequest(number = 1),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
),
|
||||
)
|
||||
} returns Throwable("Fail").asFailure()
|
||||
coEvery { vaultDiskSource.saveCipher(userId, createMockCipher(number = 1)) } just runs
|
||||
|
||||
val result = vaultRepository.shareCipher(
|
||||
cipherId = "mockId-1",
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
ShareCipherResult.Error(errorMessage = null),
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `shareCipher with cipherService encryptCipher failure should return ShareCipherResultError`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
coEvery {
|
||||
vaultSdkSource.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
)
|
||||
} returns Throwable("Fail").asFailure()
|
||||
coEvery {
|
||||
ciphersService.shareCipher(
|
||||
cipherId = "mockId-1",
|
||||
body = ShareCipherJsonRequest(
|
||||
cipher = createMockCipherJsonRequest(number = 1),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
),
|
||||
)
|
||||
} returns createMockCipher(number = 1).asSuccess()
|
||||
coEvery { vaultDiskSource.saveCipher(userId, createMockCipher(number = 1)) } just runs
|
||||
|
||||
val result = vaultRepository.shareCipher(
|
||||
cipherId = "mockId-1",
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
ShareCipherResult.Error(errorMessage = null),
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
//region Helper functions
|
||||
|
||||
/**
|
||||
|
||||
+21
-41
@@ -84,11 +84,11 @@ class VaultMoveToOrganizationScreenTest : BaseComposeTest() {
|
||||
@Test
|
||||
fun `selecting an organization should send OrganizationSelect action`() {
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Organization, Organization 1")
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Organization, mockOrganizationName-1")
|
||||
.performClick()
|
||||
// Choose the option from the menu
|
||||
composeTestRule
|
||||
.onAllNodesWithText(text = "Organization 2")
|
||||
.onAllNodesWithText(text = "mockOrganizationName-2")
|
||||
.onLast()
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
@@ -97,22 +97,12 @@ class VaultMoveToOrganizationScreenTest : BaseComposeTest() {
|
||||
viewModel.trySendAction(
|
||||
VaultMoveToOrganizationAction.OrganizationSelect(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Organization(
|
||||
id = "2",
|
||||
name = "Organization 2",
|
||||
id = "mockOrganizationId-2",
|
||||
name = "mockOrganizationName-2",
|
||||
collections = listOf(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "1",
|
||||
name = "Collection 1",
|
||||
isSelected = true,
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "2",
|
||||
name = "Collection 2",
|
||||
isSelected = false,
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "3",
|
||||
name = "Collection 3",
|
||||
id = "mockId-2",
|
||||
name = "mockName-2",
|
||||
isSelected = false,
|
||||
),
|
||||
),
|
||||
@@ -125,36 +115,36 @@ class VaultMoveToOrganizationScreenTest : BaseComposeTest() {
|
||||
@Test
|
||||
fun `the organization option field should display according to state`() {
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Organization, Organization 1")
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Organization, mockOrganizationName-1")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.copy(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "2",
|
||||
selectedOrganizationId = "mockOrganizationId-2",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Organization, Organization 2")
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Organization, mockOrganizationName-2")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `selecting a collection should send CollectionSelect action`() {
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Collection 2")
|
||||
.onNodeWithText(text = "mockName-1")
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultMoveToOrganizationAction.CollectionSelect(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "2",
|
||||
name = "Collection 2",
|
||||
isSelected = false,
|
||||
id = "mockId-1",
|
||||
name = "mockName-1",
|
||||
isSelected = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -164,14 +154,8 @@ class VaultMoveToOrganizationScreenTest : BaseComposeTest() {
|
||||
@Test
|
||||
fun `the collection list should display according to state`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Collection 1")
|
||||
.onNodeWithText("mockName-1")
|
||||
.assertIsOn()
|
||||
composeTestRule
|
||||
.onNodeWithText("Collection 2")
|
||||
.assertIsOff()
|
||||
composeTestRule
|
||||
.onNodeWithText("Collection 3")
|
||||
.assertIsOff()
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.copy(
|
||||
@@ -180,31 +164,27 @@ class VaultMoveToOrganizationScreenTest : BaseComposeTest() {
|
||||
.map { organization ->
|
||||
organization.copy(
|
||||
collections =
|
||||
if (organization.id == "1") {
|
||||
if (organization.id == "mockOrganizationId-1") {
|
||||
organization
|
||||
.collections
|
||||
.map { collection ->
|
||||
collection.copy(isSelected = collection.id != "1")
|
||||
collection.copy(
|
||||
isSelected = collection.id != "mockId-1",
|
||||
)
|
||||
}
|
||||
} else {
|
||||
organization.collections
|
||||
},
|
||||
)
|
||||
},
|
||||
selectedOrganizationId = "1",
|
||||
selectedOrganizationId = "mockOrganizationId-1",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Collection 1")
|
||||
.onNodeWithText("mockName-1")
|
||||
.assertIsOff()
|
||||
composeTestRule
|
||||
.onNodeWithText("Collection 2")
|
||||
.assertIsOn()
|
||||
composeTestRule
|
||||
.onNodeWithText("Collection 3")
|
||||
.assertIsOn()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -251,7 +231,7 @@ private fun createVaultMoveToOrganizationState(): VaultMoveToOrganizationState =
|
||||
vaultItemId = "mockId",
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "1",
|
||||
selectedOrganizationId = "mockOrganizationId-1",
|
||||
),
|
||||
dialogState = null,
|
||||
)
|
||||
+320
-69
@@ -2,10 +2,27 @@ package com.x8bit.bitwarden.ui.vault.feature.movetoorganization
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.CollectionView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.Organization
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
||||
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.util.createMockOrganizationList
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -17,6 +34,22 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() {
|
||||
state = initialState,
|
||||
)
|
||||
|
||||
private val mutableVaultItemFlow = MutableStateFlow<DataState<CipherView?>>(DataState.Loading)
|
||||
|
||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(DEFAULT_USER_STATE)
|
||||
|
||||
private val mutableCollectionFlow =
|
||||
MutableStateFlow<DataState<List<CollectionView>>>(DataState.Loading)
|
||||
|
||||
private val vaultRepository: VaultRepository = mockk {
|
||||
every { getVaultItemStateFlow(DEFAULT_ITEM_ID) } returns mutableVaultItemFlow
|
||||
every { collectionsStateFlow } returns mutableCollectionFlow
|
||||
}
|
||||
|
||||
private val authRepository: AuthRepository = mockk {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct when state is null`() = runTest {
|
||||
val viewModel = createViewModel(
|
||||
@@ -32,14 +65,13 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct`() = runTest {
|
||||
val initState = createVaultMoveToOrganizationState()
|
||||
val viewModel = createViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = initState,
|
||||
state = initialState,
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(initState, viewModel.stateFlow.value)
|
||||
assertEquals(initialState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -55,27 +87,21 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `OrganizationSelect should update selected Organization`() = runTest {
|
||||
val viewModel = createViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = createVaultMoveToOrganizationState(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "1",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
mutableCollectionFlow.tryEmit(value = DataState.Loaded(DEFAULT_COLLECTIONS))
|
||||
mutableVaultItemFlow.tryEmit(value = DataState.Loaded(createMockCipherView(number = 1)))
|
||||
val action = VaultMoveToOrganizationAction.OrganizationSelect(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Organization(
|
||||
id = "3",
|
||||
name = "Organization 3",
|
||||
id = "mockOrganizationId-3",
|
||||
name = "mockOrganizationName-3",
|
||||
collections = emptyList(),
|
||||
),
|
||||
)
|
||||
val expectedState = createVaultMoveToOrganizationState(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "3",
|
||||
selectedOrganizationId = "mockOrganizationId-3",
|
||||
cipherToMove = createMockCipherView(number = 1),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -89,50 +115,34 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `CollectionSelect should update selected Collections`() = runTest {
|
||||
val viewModel = createViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = createVaultMoveToOrganizationState(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "1",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val selectCollection3Action = VaultMoveToOrganizationAction.CollectionSelect(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "3",
|
||||
name = "Collection 3",
|
||||
isSelected = false,
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
mutableCollectionFlow.tryEmit(value = DataState.Loaded(DEFAULT_COLLECTIONS))
|
||||
mutableVaultItemFlow.tryEmit(value = DataState.Loaded(createMockCipherView(number = 1)))
|
||||
val unselectCollection1Action = VaultMoveToOrganizationAction.CollectionSelect(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "1",
|
||||
name = "Collection 1",
|
||||
id = "mockId-1",
|
||||
name = "mockName-1",
|
||||
isSelected = true,
|
||||
),
|
||||
)
|
||||
val expectedState = createVaultMoveToOrganizationState(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList()
|
||||
.map { organization ->
|
||||
organization.copy(
|
||||
collections =
|
||||
if (organization.id == "1") {
|
||||
organization.collections.map {
|
||||
it.copy(isSelected = it.id == "3")
|
||||
}
|
||||
} else {
|
||||
organization.collections
|
||||
},
|
||||
)
|
||||
},
|
||||
selectedOrganizationId = "1",
|
||||
organizations = createMockOrganizationList().map { organization ->
|
||||
organization.copy(
|
||||
collections = if (organization.id == "mockOrganizationId-1") {
|
||||
organization.collections.map {
|
||||
it.copy(isSelected = false)
|
||||
}
|
||||
} else {
|
||||
organization.collections
|
||||
},
|
||||
)
|
||||
},
|
||||
cipherToMove = createMockCipherView(number = 1),
|
||||
selectedOrganizationId = "mockOrganizationId-1",
|
||||
),
|
||||
)
|
||||
|
||||
viewModel.actionChannel.trySend(selectCollection3Action)
|
||||
viewModel.actionChannel.trySend(unselectCollection1Action)
|
||||
|
||||
assertEquals(
|
||||
@@ -142,40 +152,243 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `MoveClick should show dialog, and remove it once an item is moved`() = runTest {
|
||||
val viewModel = createViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = initialState,
|
||||
fun `DataState Loading should show loading state`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
mutableCollectionFlow.tryEmit(value = DataState.Loaded(DEFAULT_COLLECTIONS))
|
||||
mutableVaultItemFlow.tryEmit(value = DataState.Loading)
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
initialState.copy(viewState = VaultMoveToOrganizationState.ViewState.Loading),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DataState Pending should show content state`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
mutableCollectionFlow.tryEmit(value = DataState.Loaded(DEFAULT_COLLECTIONS))
|
||||
mutableVaultItemFlow.tryEmit(
|
||||
value = DataState.Pending(
|
||||
data = createMockCipherView(number = 1),
|
||||
),
|
||||
)
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(initialState, awaitItem())
|
||||
assertEquals(
|
||||
initialState.copy(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "mockOrganizationId-1",
|
||||
cipherToMove = createMockCipherView(number = 1),
|
||||
), dialogState = null,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DataState Error should show Error State`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
mutableCollectionFlow.tryEmit(value = DataState.Loaded(DEFAULT_COLLECTIONS))
|
||||
mutableVaultItemFlow.tryEmit(
|
||||
value = DataState.Error(
|
||||
error = IllegalStateException(),
|
||||
data = null,
|
||||
),
|
||||
)
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
initialState.copy(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Error(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DataState NoNetwork should show Error Dialog`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
mutableCollectionFlow.tryEmit(value = DataState.Loaded(DEFAULT_COLLECTIONS))
|
||||
mutableVaultItemFlow.tryEmit(
|
||||
value = DataState.NoNetwork(),
|
||||
)
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
initialState.copy(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Error(
|
||||
message = R.string.internet_connection_required_title
|
||||
.asText()
|
||||
.concat(R.string.internet_connection_required_message.asText()),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `MoveClick with shareCipher success should show loading dialog, and remove it`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
mutableCollectionFlow.tryEmit(value = DataState.Loaded(DEFAULT_COLLECTIONS))
|
||||
mutableVaultItemFlow.tryEmit(value = DataState.Loaded(createMockCipherView(number = 1)))
|
||||
coEvery {
|
||||
vaultRepository.shareCipher(
|
||||
cipherId = "mockCipherId",
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
)
|
||||
} returns ShareCipherResult.Success
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
initialState.copy(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "mockOrganizationId-1",
|
||||
cipherToMove = createMockCipherView(number = 1),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
viewModel.actionChannel.trySend(VaultMoveToOrganizationAction.MoveClick)
|
||||
assertEquals(
|
||||
initialState.copy(
|
||||
dialogState = VaultMoveToOrganizationState.DialogState.Loading(
|
||||
message = R.string.saving.asText(),
|
||||
),
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "mockOrganizationId-1",
|
||||
cipherToMove = createMockCipherView(number = 1),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
initialState,
|
||||
initialState.copy(
|
||||
dialogState = null,
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "mockOrganizationId-1",
|
||||
cipherToMove = createMockCipherView(number = 1),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
coVerify {
|
||||
vaultRepository.shareCipher(
|
||||
cipherId = "mockCipherId",
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `MoveClick with shareCipher error should show error dialog`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
mutableCollectionFlow.tryEmit(value = DataState.Loaded(DEFAULT_COLLECTIONS))
|
||||
mutableVaultItemFlow.tryEmit(value = DataState.Loaded(createMockCipherView(number = 1)))
|
||||
coEvery {
|
||||
vaultRepository.shareCipher(
|
||||
cipherId = "mockCipherId",
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
)
|
||||
} returns ShareCipherResult.Error(errorMessage = null)
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
initialState.copy(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "mockOrganizationId-1",
|
||||
cipherToMove = createMockCipherView(number = 1),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
viewModel.actionChannel.trySend(VaultMoveToOrganizationAction.MoveClick)
|
||||
assertEquals(
|
||||
initialState.copy(
|
||||
dialogState = VaultMoveToOrganizationState.DialogState.Loading(
|
||||
message = R.string.saving.asText(),
|
||||
),
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "mockOrganizationId-1",
|
||||
cipherToMove = createMockCipherView(number = 1),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
initialState.copy(
|
||||
dialogState = VaultMoveToOrganizationState.DialogState.Error(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "mockOrganizationId-1",
|
||||
cipherToMove = createMockCipherView(number = 1),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
coVerify {
|
||||
vaultRepository.shareCipher(
|
||||
cipherId = "mockCipherId",
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `MoveClick with shareCipher success should emit NavigateBack`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
mutableCollectionFlow.tryEmit(value = DataState.Loaded(DEFAULT_COLLECTIONS))
|
||||
mutableVaultItemFlow.tryEmit(value = DataState.Loaded(createMockCipherView(number = 1)))
|
||||
coEvery {
|
||||
vaultRepository.shareCipher(
|
||||
cipherId = "mockCipherId",
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
)
|
||||
} returns ShareCipherResult.Success
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultMoveToOrganizationAction.MoveClick)
|
||||
assertEquals(
|
||||
VaultMoveToOrganizationEvent.NavigateBack,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
coVerify {
|
||||
vaultRepository.shareCipher(
|
||||
cipherId = "mockCipherId",
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
collectionIds = listOf("mockId-1"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
savedStateHandle: SavedStateHandle = initialSavedStateHandle,
|
||||
): VaultMoveToOrganizationViewModel =
|
||||
VaultMoveToOrganizationViewModel(
|
||||
savedStateHandle = savedStateHandle,
|
||||
)
|
||||
vaultRepo: VaultRepository = vaultRepository,
|
||||
authRepo: AuthRepository = authRepository,
|
||||
): VaultMoveToOrganizationViewModel = VaultMoveToOrganizationViewModel(
|
||||
savedStateHandle = savedStateHandle,
|
||||
authRepository = authRepo,
|
||||
vaultRepository = vaultRepo,
|
||||
)
|
||||
|
||||
private fun createSavedStateHandleWithState(
|
||||
state: VaultMoveToOrganizationState? = null,
|
||||
vaultItemId: String = "mockId",
|
||||
vaultItemId: String = "mockCipherId",
|
||||
) = SavedStateHandle().apply {
|
||||
set("state", state)
|
||||
set("vault_move_to_organization_id", vaultItemId)
|
||||
@@ -183,13 +396,51 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun createVaultMoveToOrganizationState(
|
||||
viewState: VaultMoveToOrganizationState.ViewState = VaultMoveToOrganizationState.ViewState.Empty,
|
||||
vaultItemId: String = "mockId",
|
||||
viewState: VaultMoveToOrganizationState.ViewState = VaultMoveToOrganizationState.ViewState.Loading,
|
||||
vaultItemId: String = "mockCipherId",
|
||||
dialogState: VaultMoveToOrganizationState.DialogState? = null,
|
||||
): VaultMoveToOrganizationState =
|
||||
VaultMoveToOrganizationState(
|
||||
vaultItemId = vaultItemId,
|
||||
viewState = viewState,
|
||||
dialogState = dialogState,
|
||||
)
|
||||
): VaultMoveToOrganizationState = VaultMoveToOrganizationState(
|
||||
vaultItemId = vaultItemId,
|
||||
viewState = viewState,
|
||||
dialogState = dialogState,
|
||||
)
|
||||
}
|
||||
|
||||
private const val DEFAULT_ITEM_ID: String = "mockCipherId"
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "Active User",
|
||||
email = "active@bitwarden.com",
|
||||
avatarColorHex = "#aa00aa",
|
||||
environment = Environment.Us,
|
||||
isPremium = true,
|
||||
isLoggedIn = true,
|
||||
isVaultUnlocked = true,
|
||||
organizations = listOf(
|
||||
Organization(
|
||||
id = "mockOrganizationId-1",
|
||||
name = "mockOrganizationName-1",
|
||||
),
|
||||
Organization(
|
||||
id = "mockOrganizationId-2",
|
||||
name = "mockOrganizationName-2",
|
||||
),
|
||||
Organization(
|
||||
id = "mockOrganizationId-3",
|
||||
name = "mockOrganizationName-3",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private val DEFAULT_COLLECTIONS = listOf(
|
||||
createMockCollectionView(number = 1),
|
||||
createMockCollectionView(number = 2),
|
||||
createMockCollectionView(number = 3),
|
||||
createMockCollectionView(number = 4),
|
||||
)
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.movetoorganization.util
|
||||
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.Organization
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.VaultMoveToOrganizationState
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class VaultMoveToOrganizationExtensionsTest {
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `toViewState should transform a valid triple of CipherView, CollectionView list, and UserState into Content ViewState`() {
|
||||
val triple = Triple(
|
||||
first = createMockCipherView(number = 1),
|
||||
second = listOf(
|
||||
createMockCollectionView(number = 1),
|
||||
createMockCollectionView(number = 2),
|
||||
createMockCollectionView(number = 3),
|
||||
),
|
||||
third = createMockUserState(),
|
||||
)
|
||||
|
||||
val result = triple.toViewState()
|
||||
|
||||
assertEquals(
|
||||
VaultMoveToOrganizationState.ViewState.Content(
|
||||
selectedOrganizationId = "mockOrganizationId-1",
|
||||
organizations = createMockOrganizationList(),
|
||||
cipherToMove = createMockCipherView(number = 1),
|
||||
),
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `toViewState should transform a triple of null CipherView, CollectionView list, and UserState into Error ViewState`() {
|
||||
val triple = Triple(
|
||||
first = null,
|
||||
second = listOf(
|
||||
createMockCollectionView(number = 1),
|
||||
createMockCollectionView(number = 2),
|
||||
createMockCollectionView(number = 3),
|
||||
),
|
||||
third = createMockUserState(),
|
||||
)
|
||||
|
||||
val result = triple.toViewState()
|
||||
|
||||
assertEquals(
|
||||
VaultMoveToOrganizationState.ViewState.Error(R.string.generic_error_message.asText()),
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `toViewState should transform a triple of CipherView, CollectionView list, and UserState without organizations into Empty ViewState`() {
|
||||
val triple = Triple(
|
||||
first = createMockCipherView(number = 1),
|
||||
second = listOf(
|
||||
createMockCollectionView(number = 1),
|
||||
createMockCollectionView(number = 2),
|
||||
createMockCollectionView(number = 3),
|
||||
),
|
||||
third = createMockUserState(hasOrganizations = false),
|
||||
)
|
||||
|
||||
val result = triple.toViewState()
|
||||
|
||||
assertEquals(
|
||||
VaultMoveToOrganizationState.ViewState.Empty,
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMockUserState(hasOrganizations: Boolean = true): UserState =
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "Active User",
|
||||
email = "active@bitwarden.com",
|
||||
avatarColorHex = "#aa00aa",
|
||||
environment = Environment.Us,
|
||||
isPremium = true,
|
||||
isLoggedIn = true,
|
||||
isVaultUnlocked = true,
|
||||
organizations = if (hasOrganizations) {
|
||||
listOf(
|
||||
Organization(
|
||||
id = "mockOrganizationId-1",
|
||||
name = "mockOrganizationName-1",
|
||||
),
|
||||
Organization(
|
||||
id = "mockOrganizationId-2",
|
||||
name = "mockOrganizationName-2",
|
||||
),
|
||||
Organization(
|
||||
id = "mockOrganizationId-3",
|
||||
name = "mockOrganizationName-3",
|
||||
),
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
+21
-46
@@ -3,56 +3,31 @@ package com.x8bit.bitwarden.ui.vault.feature.movetoorganization.util
|
||||
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.VaultMoveToOrganizationState
|
||||
|
||||
/**
|
||||
* Creates a list of mock organizations.
|
||||
* Creates a list of mock [VaultMoveToOrganizationState.ViewState.Content.Organization].
|
||||
*/
|
||||
fun createMockOrganizationList():
|
||||
List<VaultMoveToOrganizationState.ViewState.Content.Organization> =
|
||||
listOf(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Organization(
|
||||
id = "1",
|
||||
name = "Organization 1",
|
||||
collections = listOf(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "1",
|
||||
name = "Collection 1",
|
||||
isSelected = true,
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "2",
|
||||
name = "Collection 2",
|
||||
isSelected = false,
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "3",
|
||||
name = "Collection 3",
|
||||
isSelected = false,
|
||||
),
|
||||
createMockOrganization(number = 1),
|
||||
createMockOrganization(number = 2, isCollectionSelected = false),
|
||||
createMockOrganization(number = 3, isCollectionSelected = false),
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a [VaultMoveToOrganizationState.ViewState.Content.Organization] with a given number.
|
||||
*/
|
||||
fun createMockOrganization(
|
||||
number: Int,
|
||||
isCollectionSelected: Boolean = true,
|
||||
): VaultMoveToOrganizationState.ViewState.Content.Organization =
|
||||
VaultMoveToOrganizationState.ViewState.Content.Organization(
|
||||
id = "mockOrganizationId-$number",
|
||||
name = "mockOrganizationName-$number",
|
||||
collections = listOf(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "mockId-$number",
|
||||
name = "mockName-$number",
|
||||
isSelected = isCollectionSelected,
|
||||
),
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Organization(
|
||||
id = "2",
|
||||
name = "Organization 2",
|
||||
collections = listOf(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "1",
|
||||
name = "Collection 1",
|
||||
isSelected = true,
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "2",
|
||||
name = "Collection 2",
|
||||
isSelected = false,
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "3",
|
||||
name = "Collection 3",
|
||||
isSelected = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Organization(
|
||||
id = "3",
|
||||
name = "Organization 3",
|
||||
collections = emptyList(),
|
||||
),
|
||||
)
|
||||
Reference in new issue
Block a user