mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 10:48:47 +03:00
Add the underlying support for deleting a Cipher (#587)
This commit is contained in:
parent
b29af03b27
commit
1f0a1bba6f
13 changed files with 129 additions and 1 deletions
|
@ -19,6 +19,11 @@ interface VaultDiskSource {
|
|||
*/
|
||||
fun getCiphers(userId: String): Flow<List<SyncResponseJson.Cipher>>
|
||||
|
||||
/**
|
||||
* Deletes a cipher from the data source for the given [userId] and [cipherId].
|
||||
*/
|
||||
suspend fun deleteCipher(userId: String, cipherId: String)
|
||||
|
||||
/**
|
||||
* Saves a collection to the data source for the given [userId].
|
||||
*/
|
||||
|
|
|
@ -64,6 +64,10 @@ class VaultDiskSourceImpl(
|
|||
},
|
||||
)
|
||||
|
||||
override suspend fun deleteCipher(userId: String, cipherId: String) {
|
||||
ciphersDao.deleteCipher(userId, cipherId)
|
||||
}
|
||||
|
||||
override suspend fun saveCollection(userId: String, collection: SyncResponseJson.Collection) {
|
||||
collectionsDao.insertCollection(
|
||||
collection = CollectionEntity(
|
||||
|
|
|
@ -36,6 +36,13 @@ interface CiphersDao {
|
|||
@Query("DELETE FROM ciphers WHERE user_id = :userId")
|
||||
suspend fun deleteAllCiphers(userId: String): Int
|
||||
|
||||
/**
|
||||
* Deletes the specified cipher associated with the given [userId] and [cipherId]. This will
|
||||
* return the number of rows deleted by this query.
|
||||
*/
|
||||
@Query("DELETE FROM sends WHERE user_id = :userId AND id = :cipherId")
|
||||
suspend fun deleteCipher(userId: String, cipherId: String): Int
|
||||
|
||||
/**
|
||||
* Deletes all the stored ciphers associated with the given [userId] and then add all new
|
||||
* [ciphers] to the database. This will return `true` if any changes were made to the database
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.vault.datasource.network.api
|
|||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
|
@ -26,4 +27,12 @@ interface CiphersApi {
|
|||
@Path("cipherId") cipherId: String,
|
||||
@Body body: CipherJsonRequest,
|
||||
): Result<SyncResponseJson.Cipher>
|
||||
|
||||
/**
|
||||
* Deletes a cipher.
|
||||
*/
|
||||
@DELETE("ciphers/{cipherId}")
|
||||
suspend fun deleteCipher(
|
||||
@Path("cipherId") cipherId: String,
|
||||
): Result<Unit>
|
||||
}
|
||||
|
|
|
@ -20,4 +20,9 @@ interface CiphersService {
|
|||
cipherId: String,
|
||||
body: CipherJsonRequest,
|
||||
): Result<UpdateCipherResponseJson>
|
||||
|
||||
/**
|
||||
* Attempt to delete a cipher.
|
||||
*/
|
||||
suspend fun deleteCipher(cipherId: String): Result<Unit>
|
||||
}
|
||||
|
|
|
@ -34,4 +34,7 @@ class CiphersServiceImpl constructor(
|
|||
)
|
||||
?: throw throwable
|
||||
}
|
||||
|
||||
override suspend fun deleteCipher(cipherId: String): Result<Unit> =
|
||||
ciphersApi.deleteCipher(cipherId = cipherId)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
|||
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
||||
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
|
||||
|
@ -133,6 +134,11 @@ interface VaultRepository : VaultLockManager {
|
|||
*/
|
||||
suspend fun createCipher(cipherView: CipherView): CreateCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to delete a cipher.
|
||||
*/
|
||||
suspend fun deleteCipher(cipherId: String): DeleteCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to update a cipher.
|
||||
*/
|
||||
|
|
|
@ -30,6 +30,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
|||
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
||||
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
|
||||
|
@ -348,6 +349,17 @@ class VaultRepositoryImpl(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun deleteCipher(cipherId: String): DeleteCipherResult {
|
||||
val userId = requireNotNull(activeUserId)
|
||||
return ciphersService
|
||||
.deleteCipher(cipherId)
|
||||
.onSuccess { vaultDiskSource.deleteCipher(userId, cipherId) }
|
||||
.fold(
|
||||
onSuccess = { DeleteCipherResult.Success },
|
||||
onFailure = { DeleteCipherResult.Error },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun updateCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository.model
|
||||
|
||||
/**
|
||||
* Models result of deleting a cipher.
|
||||
*/
|
||||
sealed class DeleteCipherResult {
|
||||
|
||||
/**
|
||||
* Cipher deleted successfully.
|
||||
*/
|
||||
data object Success : DeleteCipherResult()
|
||||
|
||||
/**
|
||||
* Generic error while deleting a cipher.
|
||||
*/
|
||||
data object Error : DeleteCipherResult()
|
||||
}
|
|
@ -82,6 +82,18 @@ class VaultDiskSourceTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DeleteCipher should call deleteCipher`() = runTest {
|
||||
assertFalse(ciphersDao.deleteCipherCalled)
|
||||
ciphersDao.storedCiphers.add(CIPHER_ENTITY)
|
||||
assertEquals(1, ciphersDao.storedCiphers.size)
|
||||
|
||||
vaultDiskSource.deleteCipher(USER_ID, CIPHER_1.id)
|
||||
|
||||
assertTrue(ciphersDao.deleteCipherCalled)
|
||||
assertEquals(emptyList<CipherEntity>(), ciphersDao.storedCiphers)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `saveCollection should call insertCollection`() = runTest {
|
||||
assertFalse(collectionsDao.insertCollectionCalled)
|
||||
|
|
|
@ -9,6 +9,7 @@ class FakeCiphersDao : CiphersDao {
|
|||
|
||||
val storedCiphers = mutableListOf<CipherEntity>()
|
||||
|
||||
var deleteCipherCalled: Boolean = false
|
||||
var deleteCiphersCalled: Boolean = false
|
||||
var insertCiphersCalled: Boolean = false
|
||||
|
||||
|
@ -26,6 +27,14 @@ class FakeCiphersDao : CiphersDao {
|
|||
return count
|
||||
}
|
||||
|
||||
override suspend fun deleteCipher(userId: String, cipherId: String): Int {
|
||||
deleteCipherCalled = true
|
||||
val count = storedCiphers.count { it.userId == userId && it.id == cipherId }
|
||||
storedCiphers.removeAll { it.userId == userId && it.id == cipherId }
|
||||
ciphersFlow.tryEmit(storedCiphers.toList())
|
||||
return count
|
||||
}
|
||||
|
||||
override fun getAllCiphers(userId: String): Flow<List<CipherEntity>> =
|
||||
ciphersFlow.map { ciphers -> ciphers.filter { it.userId == userId } }
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipher
|
|||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipherJsonRequest
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import retrofit2.create
|
||||
|
||||
class CiphersServiceTest : BaseServiceTest() {
|
||||
|
@ -63,6 +63,14 @@ class CiphersServiceTest : BaseServiceTest() {
|
|||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `deleteCipher should execute the delete cipher API`() = runTest {
|
||||
server.enqueue(MockResponse().setResponseCode(200))
|
||||
val cipherId = "cipherId"
|
||||
val result = ciphersService.deleteCipher(cipherId = cipherId)
|
||||
assertEquals(Unit, result.getOrThrow())
|
||||
}
|
||||
}
|
||||
|
||||
private const val CREATE_UPDATE_CIPHER_SUCCESS_JSON = """
|
||||
|
|
|
@ -49,6 +49,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView
|
|||
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
||||
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
|
||||
|
@ -1390,6 +1391,36 @@ class VaultRepositoryTest {
|
|||
assertEquals(UpdateCipherResult.Success, result)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `deleteCipher with ciphersService deleteCipher failure should return DeleteCipherResult Error`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val cipherId = "mockId-1"
|
||||
coEvery {
|
||||
ciphersService.deleteCipher(cipherId = cipherId)
|
||||
} returns Throwable("Fail").asFailure()
|
||||
|
||||
val result = vaultRepository.deleteCipher(cipherId)
|
||||
|
||||
assertEquals(DeleteCipherResult.Error, result)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `deleteCipher with ciphersService deleteCipher success should return DeleteCipherResult success`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val cipherId = "mockId-1"
|
||||
coEvery { ciphersService.deleteCipher(cipherId = cipherId) } returns Unit.asSuccess()
|
||||
coEvery { vaultDiskSource.deleteCipher(userId, cipherId) } just runs
|
||||
|
||||
val result = vaultRepository.deleteCipher(cipherId)
|
||||
|
||||
assertEquals(DeleteCipherResult.Success, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createSend with encryptSend failure should return CreateSendResult failure`() =
|
||||
runTest {
|
||||
|
|
Loading…
Add table
Reference in a new issue