mirror of
https://github.com/bitwarden/android.git
synced 2025-02-17 04:19:54 +03:00
Add API support for deleting an attachment (#767)
This commit is contained in:
parent
049930da40
commit
6223b225c5
8 changed files with 172 additions and 0 deletions
|
@ -54,6 +54,15 @@ interface CiphersApi {
|
|||
@Path("cipherId") cipherId: String,
|
||||
): Result<Unit>
|
||||
|
||||
/**
|
||||
* Deletes an attachment from a cipher.
|
||||
*/
|
||||
@DELETE("ciphers/{cipherId}/attachment/{attachmentId}")
|
||||
suspend fun deleteCipherAttachment(
|
||||
@Path("cipherId") cipherId: String,
|
||||
@Path("attachmentId") attachmentId: String,
|
||||
): Result<Unit>
|
||||
|
||||
/**
|
||||
* Restores a cipher.
|
||||
*/
|
||||
|
|
|
@ -40,6 +40,14 @@ interface CiphersService {
|
|||
*/
|
||||
suspend fun softDeleteCipher(cipherId: String): Result<Unit>
|
||||
|
||||
/**
|
||||
* Attempt to delete an attachment from a cipher.
|
||||
*/
|
||||
suspend fun deleteCipherAttachment(
|
||||
cipherId: String,
|
||||
attachmentId: String,
|
||||
): Result<Unit>
|
||||
|
||||
/**
|
||||
* Attempt to restore a cipher.
|
||||
*/
|
||||
|
|
|
@ -51,6 +51,15 @@ class CiphersServiceImpl constructor(
|
|||
override suspend fun softDeleteCipher(cipherId: String): Result<Unit> =
|
||||
ciphersApi.softDeleteCipher(cipherId = cipherId)
|
||||
|
||||
override suspend fun deleteCipherAttachment(
|
||||
cipherId: String,
|
||||
attachmentId: String,
|
||||
): Result<Unit> =
|
||||
ciphersApi.deleteCipherAttachment(
|
||||
cipherId = cipherId,
|
||||
attachmentId = attachmentId,
|
||||
)
|
||||
|
||||
override suspend fun restoreCipher(cipherId: String): Result<Unit> =
|
||||
ciphersApi.restoreCipher(cipherId = cipherId)
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
|||
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
||||
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.DeleteAttachmentResult
|
||||
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.GenerateTotpResult
|
||||
|
@ -241,4 +242,13 @@ interface VaultRepository : VaultLockManager {
|
|||
* Attempt to delete a send.
|
||||
*/
|
||||
suspend fun deleteSend(sendId: String): DeleteSendResult
|
||||
|
||||
/**
|
||||
* Attempt to delete an attachment from a send.
|
||||
*/
|
||||
suspend fun deleteCipherAttachment(
|
||||
cipherId: String,
|
||||
attachmentId: String,
|
||||
cipherView: CipherView,
|
||||
): DeleteAttachmentResult
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
|||
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
||||
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.DeleteAttachmentResult
|
||||
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.GenerateTotpResult
|
||||
|
@ -487,6 +488,40 @@ class VaultRepositoryImpl(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun deleteCipherAttachment(
|
||||
cipherId: String,
|
||||
attachmentId: String,
|
||||
cipherView: CipherView,
|
||||
): DeleteAttachmentResult {
|
||||
val userId = requireNotNull(activeUserId)
|
||||
return ciphersService
|
||||
.deleteCipherAttachment(
|
||||
cipherId = cipherId,
|
||||
attachmentId = attachmentId,
|
||||
)
|
||||
.flatMap {
|
||||
vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView.copy(
|
||||
attachments = cipherView.attachments?.mapNotNull {
|
||||
if (it.id == attachmentId) null else it
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
.onSuccess { cipher ->
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = cipher.toEncryptedNetworkCipherResponse(),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { DeleteAttachmentResult.Success },
|
||||
onFailure = { DeleteAttachmentResult.Error },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun restoreCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository.model
|
||||
|
||||
/**
|
||||
* Models result of deleting an attachment from a cipher.
|
||||
*/
|
||||
sealed class DeleteAttachmentResult {
|
||||
|
||||
/**
|
||||
* Attachment deleted successfully.
|
||||
*/
|
||||
data object Success : DeleteAttachmentResult()
|
||||
|
||||
/**
|
||||
* Generic error while deleting an attachment.
|
||||
*/
|
||||
data object Error : DeleteAttachmentResult()
|
||||
}
|
|
@ -81,6 +81,18 @@ class CiphersServiceTest : BaseServiceTest() {
|
|||
assertEquals(Unit, result.getOrThrow())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `deleteCipherAttachment should execute the deleteCipherAttachment API`() = runTest {
|
||||
server.enqueue(MockResponse().setResponseCode(200))
|
||||
val cipherId = "cipherId"
|
||||
val attachmentId = "attachmentId"
|
||||
val result = ciphersService.deleteCipherAttachment(
|
||||
cipherId = cipherId,
|
||||
attachmentId = attachmentId,
|
||||
)
|
||||
assertEquals(Unit, result.getOrThrow())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `shareCipher should execute the share cipher API`() = runTest {
|
||||
server.enqueue(
|
||||
|
|
|
@ -61,6 +61,7 @@ import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
|||
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
||||
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.DeleteAttachmentResult
|
||||
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.GenerateTotpResult
|
||||
|
@ -1703,6 +1704,77 @@ class VaultRepositoryTest {
|
|||
unmockkStatic(Cipher::toEncryptedNetworkCipherResponse)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `deleteCipherAttachment with ciphersService deleteCipherAttachment failure should return DeleteAttachmentResult Error`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val cipherId = "mockId-1"
|
||||
val attachmentId = "mockId-1"
|
||||
coEvery {
|
||||
ciphersService.deleteCipherAttachment(
|
||||
cipherId = cipherId,
|
||||
attachmentId = attachmentId,
|
||||
)
|
||||
} returns Throwable("Fail").asFailure()
|
||||
|
||||
val result = vaultRepository.deleteCipherAttachment(
|
||||
cipherId = cipherId,
|
||||
attachmentId = attachmentId,
|
||||
cipherView = createMockCipherView(number = 1),
|
||||
)
|
||||
|
||||
assertEquals(DeleteAttachmentResult.Error, result)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `deleteCipherAttachment with ciphersService deleteCipherAttachment success should return DeleteAttachmentResult success`() =
|
||||
runTest {
|
||||
mockkStatic(Cipher::toEncryptedNetworkCipherResponse)
|
||||
every {
|
||||
createMockSdkCipher(number = 1).toEncryptedNetworkCipherResponse()
|
||||
} returns createMockCipher(number = 1)
|
||||
val fixedInstant = Instant.parse("2021-01-01T00:00:00Z")
|
||||
val userId = "mockId-1"
|
||||
val cipherId = "mockId-1"
|
||||
val attachmentId = "mockId-1"
|
||||
coEvery {
|
||||
vaultSdkSource.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = createMockCipherView(number = 1).copy(
|
||||
attachments = emptyList(),
|
||||
),
|
||||
)
|
||||
} returns createMockSdkCipher(number = 1).asSuccess()
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
coEvery {
|
||||
ciphersService.deleteCipherAttachment(
|
||||
cipherId = cipherId,
|
||||
attachmentId = attachmentId,
|
||||
)
|
||||
} returns Unit.asSuccess()
|
||||
coEvery {
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = createMockCipher(number = 1),
|
||||
)
|
||||
} returns Unit
|
||||
val cipherView = createMockCipherView(number = 1)
|
||||
mockkStatic(Instant::class)
|
||||
every { Instant.now() } returns fixedInstant
|
||||
|
||||
val result = vaultRepository.deleteCipherAttachment(
|
||||
cipherId = cipherId,
|
||||
attachmentId = attachmentId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
|
||||
assertEquals(DeleteAttachmentResult.Success, result)
|
||||
unmockkStatic(Instant::class)
|
||||
unmockkStatic(Cipher::toEncryptedNetworkCipherResponse)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `restoreCipher with ciphersService restoreCipher failure should return RestoreCipherResult Error`() =
|
||||
|
|
Loading…
Add table
Reference in a new issue