[BIT-2275] Fix OutOfMemoryException when saving attachments (#1418)

This commit is contained in:
Patrick Honkonen 2024-06-04 12:17:24 -04:00 committed by Álison Fernandes
parent a8f7c576fb
commit 51d65f602d
13 changed files with 274 additions and 112 deletions

View file

@ -8,6 +8,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRe
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherCollectionsJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherResponseJson
import java.io.File
/**
* Provides an API for querying ciphers endpoints.
@ -31,7 +32,7 @@ interface CiphersService {
*/
suspend fun uploadAttachment(
attachmentJsonResponse: AttachmentJsonResponse,
encryptedFile: ByteArray,
encryptedFile: File,
): Result<SyncResponseJson.Cipher>
/**

View file

@ -17,7 +17,8 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherRespo
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.time.Clock
import java.time.ZoneOffset
import java.time.ZonedDateTime
@ -48,7 +49,7 @@ class CiphersServiceImpl(
override suspend fun uploadAttachment(
attachmentJsonResponse: AttachmentJsonResponse,
encryptedFile: ByteArray,
encryptedFile: File,
): Result<SyncResponseJson.Cipher> {
val cipher = attachmentJsonResponse.cipherResponse
return when (attachmentJsonResponse.fileUploadType) {
@ -62,7 +63,7 @@ class CiphersServiceImpl(
)
.addPart(
part = MultipartBody.Part.createFormData(
body = encryptedFile.toRequestBody(
body = encryptedFile.asRequestBody(
contentType = "application/octet-stream".toMediaType(),
),
name = "data",
@ -83,7 +84,7 @@ class CiphersServiceImpl(
.RFC_1123_DATE_TIME
.format(ZonedDateTime.ofInstant(clock.instant(), ZoneOffset.UTC)),
version = attachmentJsonResponse.url.toUri().getQueryParameter("sv"),
body = encryptedFile.toRequestBody(),
body = encryptedFile.asRequestBody(),
)
}
}

View file

@ -137,8 +137,9 @@ interface VaultSdkSource {
userId: String,
cipher: Cipher,
attachmentView: AttachmentView,
fileBuffer: ByteArray,
): Result<AttachmentEncryptResult>
decryptedFilePath: String,
encryptedFilePath: String,
): Result<Attachment>
/**
* Encrypts a [CipherView] for the user with the given [userId], returning a [Cipher] wrapped

View file

@ -1,7 +1,6 @@
package com.x8bit.bitwarden.data.vault.datasource.sdk
import com.bitwarden.core.Attachment
import com.bitwarden.core.AttachmentEncryptResult
import com.bitwarden.core.AttachmentView
import com.bitwarden.core.Cipher
import com.bitwarden.core.CipherListView
@ -183,16 +182,18 @@ class VaultSdkSourceImpl(
userId: String,
cipher: Cipher,
attachmentView: AttachmentView,
fileBuffer: ByteArray,
): Result<AttachmentEncryptResult> =
decryptedFilePath: String,
encryptedFilePath: String,
): Result<Attachment> =
runCatching {
getClient(userId = userId)
.vault()
.attachments()
.encryptBuffer(
.encryptFile(
cipher = cipher,
attachment = attachmentView,
buffer = fileBuffer,
decryptedFilePath = decryptedFilePath,
encryptedFilePath = encryptedFilePath,
)
}

View file

@ -17,9 +17,9 @@ interface FileManager {
val filesDirectory: String
/**
* Deletes a [file] from the system.
* Deletes [files] from disk.
*/
suspend fun deleteFile(file: File)
suspend fun delete(vararg files: File)
/**
* Downloads a file temporarily to cache from [url]. A successful [DownloadResult] will contain

View file

@ -32,9 +32,9 @@ class FileManagerImpl(
override val filesDirectory: String
get() = context.filesDir.absolutePath
override suspend fun deleteFile(file: File) {
override suspend fun delete(vararg files: File) {
withContext(dispatcherManager.io) {
file.delete()
files.forEach { it.delete() }
}
}

View file

@ -871,6 +871,7 @@ class VaultRepositoryImpl(
)
}
@Suppress("LongMethod")
override suspend fun createAttachment(
cipherId: String,
cipherView: CipherView,
@ -894,34 +895,52 @@ class VaultRepositoryImpl(
)
.flatMap { cipher ->
fileManager
.uriToByteArray(fileUri = fileUri)
.flatMap {
vaultSdkSource.encryptAttachment(
userId = userId,
cipher = cipher,
attachmentView = attachmentView,
fileBuffer = it,
)
}
}
.flatMap { attachmentEncryptResult ->
ciphersService
.createAttachment(
cipherId = cipherId,
body = AttachmentJsonRequest(
// We know these values are present because
// - the filename/size are passed into the function
// - the SDK call fills in the key
fileName = requireNotNull(attachmentEncryptResult.attachment.fileName),
key = requireNotNull(attachmentEncryptResult.attachment.key),
fileSize = requireNotNull(attachmentEncryptResult.attachment.size),
),
)
.flatMap { attachmentJsonResponse ->
ciphersService.uploadAttachment(
attachmentJsonResponse = attachmentJsonResponse,
encryptedFile = attachmentEncryptResult.contents,
)
.writeUriToCache(fileUri = fileUri)
.flatMap { cacheFile ->
vaultSdkSource
.encryptAttachment(
userId = userId,
cipher = cipher,
attachmentView = attachmentView,
decryptedFilePath = cacheFile.absolutePath,
encryptedFilePath = "${cacheFile.absolutePath}.enc",
)
.flatMap { attachment ->
ciphersService
.createAttachment(
cipherId = cipherId,
body = AttachmentJsonRequest(
// We know these values are present because
// - the filename/size are passed into the function
// - the SDK call fills in the key
fileName = requireNotNull(attachment.fileName),
key = requireNotNull(attachment.key),
fileSize = requireNotNull(attachment.size),
),
)
.flatMap { attachmentJsonResponse ->
val encryptedFile = File("${cacheFile.absolutePath}.enc")
ciphersService
.uploadAttachment(
attachmentJsonResponse = attachmentJsonResponse,
encryptedFile = encryptedFile,
)
.onSuccess {
fileManager
.delete(
cacheFile,
encryptedFile,
)
}
.onFailure {
fileManager
.delete(
cacheFile,
encryptedFile,
)
}
}
}
}
}
.map { it.copy(collectionIds = cipherView.collectionIds) }
@ -1658,7 +1677,7 @@ class VaultRepositoryImpl(
)
.also {
// Delete encrypted file once it has been uploaded.
fileManager.deleteFile(encryptedFile)
fileManager.delete(encryptedFile)
}
.map { CreateSendJsonResponse.Success(it) }
}

View file

@ -353,7 +353,7 @@ class VaultItemViewModel @Inject constructor(
private fun handleNoAttachmentFileLocationReceive() {
viewModelScope.launch {
temporaryAttachmentData?.let { fileManager.deleteFile(it) }
temporaryAttachmentData?.let { fileManager.delete(it) }
}
mutableStateFlow.update {
@ -932,7 +932,7 @@ class VaultItemViewModel @Inject constructor(
action: VaultItemAction.Internal.AttachmentFinishedSavingToDisk,
) {
viewModelScope.launch {
fileManager.deleteFile(action.file)
fileManager.delete(action.file)
}
if (action.isSaved) {

View file

@ -1,13 +0,0 @@
package com.x8bit.bitwarden.data.vault.datasource.network.model
import com.bitwarden.core.AttachmentEncryptResult
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkAttachment
/**
* Create a mock [AttachmentEncryptResult] with a given [number].
*/
fun createMockAttachmentEncryptResult(number: Int): AttachmentEncryptResult =
AttachmentEncryptResult(
attachment = createMockSdkAttachment(number = 1),
contents = byteArrayOf(number.toByte()),
)

View file

@ -26,6 +26,7 @@ import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import retrofit2.create
import java.io.File
import java.time.Clock
import java.time.Instant
import java.time.ZoneOffset
@ -103,7 +104,7 @@ class CiphersServiceTest : BaseServiceTest() {
number = 1,
fileUploadType = FileUploadType.AZURE,
)
val encryptedFile = byteArrayOf()
val encryptedFile = File.createTempFile("mockFile", "temp")
server.enqueue(MockResponse().setResponseCode(201))
val result = ciphersService.uploadAttachment(
@ -121,7 +122,7 @@ class CiphersServiceTest : BaseServiceTest() {
number = 1,
fileUploadType = FileUploadType.DIRECT,
)
val encryptedFile = byteArrayOf()
val encryptedFile = File.createTempFile("mockFile", "temp")
server.enqueue(MockResponse().setResponseCode(201))
val result = ciphersService.uploadAttachment(

View file

@ -1,7 +1,6 @@
package com.x8bit.bitwarden.data.vault.datasource.sdk
import com.bitwarden.core.Attachment
import com.bitwarden.core.AttachmentEncryptResult
import com.bitwarden.core.AttachmentView
import com.bitwarden.core.Cipher
import com.bitwarden.core.CipherListView
@ -589,15 +588,15 @@ class VaultSdkSourceTest {
fun `encryptAttachment should call SDK and return correct data wrapped in a Result`() =
runBlocking {
val userId = "userId"
val expectedResult = mockk<AttachmentEncryptResult>()
val expectedResult = mockk<Attachment>()
val mockCipher = mockk<Cipher>()
val mockAttachmentView = mockk<AttachmentView>()
val fileBuffer = byteArrayOf(1, 2)
coEvery {
clientVault.attachments().encryptBuffer(
clientVault.attachments().encryptFile(
cipher = mockCipher,
attachment = mockAttachmentView,
buffer = fileBuffer,
decryptedFilePath = "",
encryptedFilePath = "",
)
} returns expectedResult
@ -605,15 +604,17 @@ class VaultSdkSourceTest {
userId = userId,
cipher = mockCipher,
attachmentView = mockAttachmentView,
fileBuffer = fileBuffer,
decryptedFilePath = "",
encryptedFilePath = "",
)
assertEquals(expectedResult.asSuccess(), result)
coVerify {
clientVault.attachments().encryptBuffer(
clientVault.attachments().encryptFile(
cipher = mockCipher,
attachment = mockAttachmentView,
buffer = fileBuffer,
decryptedFilePath = "",
encryptedFilePath = "",
)
}
}

View file

@ -49,7 +49,6 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherColle
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateFolderResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockAttachmentEncryptResult
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockAttachmentJsonResponse
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipher
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipherJsonRequest
@ -74,6 +73,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockAttachmentV
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.datasource.sdk.model.createMockFolderView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkAttachment
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkCipher
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkCollection
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFolder
@ -158,7 +158,9 @@ class VaultRepositoryTest {
private val userLogoutManager: UserLogoutManager = mockk {
every { logout(any(), any()) } just runs
}
private val fileManager: FileManager = mockk()
private val fileManager: FileManager = mockk {
coEvery { delete(*anyVararg()) } just runs
}
private val fakeAuthDiskSource = FakeAuthDiskSource()
private val settingsDiskSource = mockk<SettingsDiskSource> {
every { getLastSyncTime(userId = any()) } returns clock.instant()
@ -2692,7 +2694,6 @@ class VaultRepositoryTest {
} returns mockSendView.asSuccess()
every { fileManager.filesDirectory } returns "mockFilesDirectory"
coEvery { fileManager.writeUriToCache(any()) } returns decryptedFile.asSuccess()
coEvery { fileManager.deleteFile(any()) } returns Unit
coEvery {
vaultSdkSource.encryptFile(
userId = userId,
@ -2768,7 +2769,6 @@ class VaultRepositoryTest {
} returns mockSdkSend.asSuccess()
every { fileManager.filesDirectory } returns "mockFilesDirectory"
coEvery { fileManager.deleteFile(any()) } returns Unit
coEvery { fileManager.writeUriToCache(any()) } returns decryptedFile.asSuccess()
coEvery {
vaultSdkSource.encryptFile(
@ -3392,6 +3392,7 @@ class VaultRepositoryTest {
val mockUri = setupMockUri(url = "www.test.com")
val mockCipherView = createMockCipherView(number = 1)
val mockCipher = createMockSdkCipher(number = 1, clock = clock)
val mockFile = File.createTempFile("mockFile", "temp")
val mockFileName = "mockFileName-1"
val mockFileSize = "1"
val mockAttachmentView = createMockAttachmentView(number = 1).copy(
@ -3400,19 +3401,19 @@ class VaultRepositoryTest {
url = null,
key = null,
)
val mockByteArray = byteArrayOf(1, 2)
coEvery {
vaultSdkSource.encryptCipher(userId = userId, cipherView = mockCipherView)
} returns mockCipher.asSuccess()
coEvery {
fileManager.uriToByteArray(fileUri = mockUri)
} returns mockByteArray.asSuccess()
fileManager.writeUriToCache(fileUri = mockUri)
} returns mockFile.asSuccess()
coEvery {
vaultSdkSource.encryptAttachment(
userId = userId,
cipher = mockCipher,
attachmentView = mockAttachmentView,
fileBuffer = mockByteArray,
decryptedFilePath = mockFile.absolutePath,
encryptedFilePath = "${mockFile.absolutePath}.enc",
)
} returns Throwable("Fail").asFailure()
@ -3443,7 +3444,7 @@ class VaultRepositoryTest {
vaultSdkSource.encryptCipher(userId = userId, cipherView = mockCipherView)
} returns mockCipher.asSuccess()
coEvery {
fileManager.uriToByteArray(fileUri = mockUri)
fileManager.writeUriToCache(fileUri = mockUri)
} returns Throwable("Fail").asFailure()
val result = vaultRepository.createAttachment(
@ -3475,22 +3476,23 @@ class VaultRepositoryTest {
url = null,
key = null,
)
val mockByteArray = byteArrayOf(1, 2)
val mockAttachmentEncryptResult = createMockAttachmentEncryptResult(number = 1)
val mockFile = File.createTempFile("mockFile", "temp")
val mockAttachment = createMockSdkAttachment(number = 1)
coEvery {
vaultSdkSource.encryptCipher(userId = userId, cipherView = mockCipherView)
} returns mockCipher.asSuccess()
coEvery {
fileManager.uriToByteArray(fileUri = mockUri)
} returns mockByteArray.asSuccess()
fileManager.writeUriToCache(fileUri = mockUri)
} returns mockFile.asSuccess()
coEvery {
vaultSdkSource.encryptAttachment(
userId = userId,
cipher = mockCipher,
attachmentView = mockAttachmentView,
fileBuffer = mockByteArray,
decryptedFilePath = mockFile.absolutePath,
encryptedFilePath = "${mockFile.absolutePath}.enc",
)
} returns mockAttachmentEncryptResult.asSuccess()
} returns mockAttachment.asSuccess()
coEvery {
ciphersService.createAttachment(
cipherId = cipherId,
@ -3531,23 +3533,24 @@ class VaultRepositoryTest {
url = null,
key = null,
)
val mockByteArray = byteArrayOf(1, 2)
val mockAttachmentEncryptResult = createMockAttachmentEncryptResult(number = 1)
val mockFile = File.createTempFile("mockFile", "temp")
val mockAttachment = createMockSdkAttachment(number = 1)
val mockAttachmentJsonResponse = createMockAttachmentJsonResponse(number = 1)
coEvery {
vaultSdkSource.encryptCipher(userId = userId, cipherView = mockCipherView)
} returns mockCipher.asSuccess()
coEvery {
fileManager.uriToByteArray(fileUri = mockUri)
} returns mockByteArray.asSuccess()
fileManager.writeUriToCache(fileUri = mockUri)
} returns mockFile.asSuccess()
coEvery {
vaultSdkSource.encryptAttachment(
userId = userId,
cipher = mockCipher,
attachmentView = mockAttachmentView,
fileBuffer = mockByteArray,
decryptedFilePath = mockFile.absolutePath,
encryptedFilePath = "${mockFile.absolutePath}.enc",
)
} returns mockAttachmentEncryptResult.asSuccess()
} returns mockAttachment.asSuccess()
coEvery {
ciphersService.createAttachment(
cipherId = cipherId,
@ -3561,7 +3564,7 @@ class VaultRepositoryTest {
coEvery {
ciphersService.uploadAttachment(
attachmentJsonResponse = mockAttachmentJsonResponse,
encryptedFile = mockAttachmentEncryptResult.contents,
encryptedFile = File("${mockFile.absoluteFile}.enc"),
)
} returns Throwable("Fail").asFailure()
@ -3594,8 +3597,8 @@ class VaultRepositoryTest {
url = null,
key = null,
)
val mockByteArray = byteArrayOf(1, 2)
val mockAttachmentEncryptResult = createMockAttachmentEncryptResult(number = 1)
val mockFile = File.createTempFile("mockFile", "temp")
val mockAttachment = createMockSdkAttachment(number = 1)
val mockAttachmentJsonResponse = createMockAttachmentJsonResponse(number = 1)
val mockCipherResponse = createMockCipher(number = 1).copy(collectionIds = null)
val mockUpdatedCipherResponse = createMockCipher(number = 1).copy(
@ -3605,16 +3608,17 @@ class VaultRepositoryTest {
vaultSdkSource.encryptCipher(userId = userId, cipherView = mockCipherView)
} returns mockCipher.asSuccess()
coEvery {
fileManager.uriToByteArray(fileUri = mockUri)
} returns mockByteArray.asSuccess()
fileManager.writeUriToCache(fileUri = mockUri)
} returns mockFile.asSuccess()
coEvery {
vaultSdkSource.encryptAttachment(
userId = userId,
cipher = mockCipher,
attachmentView = mockAttachmentView,
fileBuffer = mockByteArray,
decryptedFilePath = mockFile.absolutePath,
encryptedFilePath = "${mockFile.absolutePath}.enc",
)
} returns mockAttachmentEncryptResult.asSuccess()
} returns mockAttachment.asSuccess()
coEvery {
ciphersService.createAttachment(
cipherId = cipherId,
@ -3628,7 +3632,7 @@ class VaultRepositoryTest {
coEvery {
ciphersService.uploadAttachment(
attachmentJsonResponse = mockAttachmentJsonResponse,
encryptedFile = mockAttachmentEncryptResult.contents,
encryptedFile = File("${mockFile.absoluteFile}.enc"),
)
} returns mockCipherResponse.asSuccess()
coEvery {
@ -3670,8 +3674,8 @@ class VaultRepositoryTest {
url = null,
key = null,
)
val mockByteArray = byteArrayOf(1, 2)
val mockAttachmentEncryptResult = createMockAttachmentEncryptResult(number = 1)
val mockFile = File.createTempFile("mockFile", "temp")
val mockAttachment = createMockSdkAttachment(number = 1)
val mockAttachmentJsonResponse = createMockAttachmentJsonResponse(number = 1)
val mockCipherResponse = createMockCipher(number = 1).copy(collectionIds = null)
val mockUpdatedCipherResponse = createMockCipher(number = 1).copy(
@ -3681,16 +3685,17 @@ class VaultRepositoryTest {
vaultSdkSource.encryptCipher(userId = userId, cipherView = mockCipherView)
} returns mockCipher.asSuccess()
coEvery {
fileManager.uriToByteArray(fileUri = mockUri)
} returns mockByteArray.asSuccess()
fileManager.writeUriToCache(fileUri = mockUri)
} returns mockFile.asSuccess()
coEvery {
vaultSdkSource.encryptAttachment(
userId = userId,
cipher = mockCipher,
attachmentView = mockAttachmentView,
fileBuffer = mockByteArray,
decryptedFilePath = mockFile.absolutePath,
encryptedFilePath = "${mockFile.absolutePath}.enc",
)
} returns mockAttachmentEncryptResult.asSuccess()
} returns mockAttachment.asSuccess()
coEvery {
ciphersService.createAttachment(
cipherId = cipherId,
@ -3704,7 +3709,7 @@ class VaultRepositoryTest {
coEvery {
ciphersService.uploadAttachment(
attachmentJsonResponse = mockAttachmentJsonResponse,
encryptedFile = mockAttachmentEncryptResult.contents,
encryptedFile = File("${mockFile.absolutePath}.enc"),
)
} returns mockCipherResponse.asSuccess()
coEvery {
@ -3728,6 +3733,151 @@ class VaultRepositoryTest {
assertEquals(CreateAttachmentResult.Success(mockCipherView), result)
}
@Test
fun `createAttachment should delete temp files after upload success`() {
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val userId = "mockId-1"
val cipherId = "cipherId-1"
val mockUri = setupMockUri(url = "www.test.com")
val mockCipherView = createMockCipherView(number = 1)
val mockCipher = createMockSdkCipher(number = 1, clock = clock)
val mockFileName = "mockFileName-1"
val mockFileSize = "1"
val mockAttachmentView = createMockAttachmentView(number = 1).copy(
sizeName = null,
id = null,
url = null,
key = null,
)
val mockFile = File.createTempFile("mockFile", "temp")
val mockAttachment = createMockSdkAttachment(number = 1)
val mockAttachmentJsonResponse = createMockAttachmentJsonResponse(number = 1)
val mockCipherResponse = createMockCipher(number = 1).copy(collectionIds = null)
val mockUpdatedCipherResponse = createMockCipher(number = 1).copy(
collectionIds = listOf("mockId-1"),
)
coEvery {
vaultSdkSource.encryptCipher(userId = userId, cipherView = mockCipherView)
} returns mockCipher.asSuccess()
coEvery {
fileManager.writeUriToCache(fileUri = mockUri)
} returns mockFile.asSuccess()
coEvery {
vaultSdkSource.encryptAttachment(
userId = userId,
cipher = mockCipher,
attachmentView = mockAttachmentView,
decryptedFilePath = mockFile.absolutePath,
encryptedFilePath = "${mockFile.absolutePath}.enc",
)
} returns mockAttachment.asSuccess()
coEvery {
ciphersService.createAttachment(
cipherId = cipherId,
body = AttachmentJsonRequest(
fileName = mockFileName,
key = "mockKey-1",
fileSize = mockFileSize,
),
)
} returns mockAttachmentJsonResponse.asSuccess()
coEvery {
ciphersService.uploadAttachment(
attachmentJsonResponse = mockAttachmentJsonResponse,
encryptedFile = File("${mockFile.absolutePath}.enc"),
)
} returns mockCipherResponse.asSuccess()
coEvery {
vaultDiskSource.saveCipher(userId = userId, cipher = mockUpdatedCipherResponse)
} just runs
coEvery {
vaultSdkSource.decryptCipher(
userId = userId,
cipher = mockUpdatedCipherResponse.toEncryptedSdkCipher(),
)
} returns mockCipherView.asSuccess()
vaultRepository.createAttachment(
cipherId = cipherId,
cipherView = mockCipherView,
fileSizeBytes = mockFileSize,
fileName = mockFileName,
fileUri = mockUri,
)
coVerify {
fileManager.delete(*anyVararg())
}
}
}
@Test
fun `createAttachment should delete temp files after upload failure`() {
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val userId = "mockId-1"
val cipherId = "cipherId-1"
val mockUri = setupMockUri(url = "www.test.com")
val mockCipherView = createMockCipherView(number = 1)
val mockCipher = createMockSdkCipher(number = 1, clock = clock)
val mockFileName = "mockFileName-1"
val mockFileSize = "1"
val mockAttachmentView = createMockAttachmentView(number = 1).copy(
sizeName = null,
id = null,
url = null,
key = null,
)
val mockFile = File.createTempFile("mockFile", "temp")
val mockAttachment = createMockSdkAttachment(number = 1)
val mockAttachmentJsonResponse = createMockAttachmentJsonResponse(number = 1)
coEvery {
vaultSdkSource.encryptCipher(userId = userId, cipherView = mockCipherView)
} returns mockCipher.asSuccess()
coEvery {
fileManager.writeUriToCache(fileUri = mockUri)
} returns mockFile.asSuccess()
coEvery {
vaultSdkSource.encryptAttachment(
userId = userId,
cipher = mockCipher,
attachmentView = mockAttachmentView,
decryptedFilePath = mockFile.absolutePath,
encryptedFilePath = "${mockFile.absolutePath}.enc",
)
} returns mockAttachment.asSuccess()
coEvery {
ciphersService.createAttachment(
cipherId = cipherId,
body = AttachmentJsonRequest(
fileName = mockFileName,
key = "mockKey-1",
fileSize = mockFileSize,
),
)
} returns mockAttachmentJsonResponse.asSuccess()
coEvery {
ciphersService.uploadAttachment(
attachmentJsonResponse = mockAttachmentJsonResponse,
encryptedFile = File("${mockFile.absolutePath}.enc"),
)
} returns Throwable("Fail").asFailure()
vaultRepository.createAttachment(
cipherId = cipherId,
cipherView = mockCipherView,
fileSizeBytes = mockFileSize,
fileName = mockFileName,
fileUri = mockUri,
)
coVerify {
fileManager.delete(*anyVararg())
}
}
}
@Test
fun `downloadAttachment with missing attachment should return Failure`() = runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE

View file

@ -1399,7 +1399,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel(state = DEFAULT_STATE, tempAttachmentFile = file)
coEvery {
mockFileManager.deleteFile(any())
mockFileManager.delete(any())
} just runs
val uri = mockk<Uri>()
@ -1424,7 +1424,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
}
coVerify(exactly = 1) {
mockFileManager.deleteFile(file)
mockFileManager.delete(file)
mockFileManager.fileToUri(uri, file)
}
}
@ -1437,7 +1437,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel(state = DEFAULT_STATE, tempAttachmentFile = file)
coEvery {
mockFileManager.deleteFile(any())
mockFileManager.delete(any())
} just runs
val uri = mockk<Uri>()
@ -1466,7 +1466,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
}
coVerify(exactly = 1) {
mockFileManager.deleteFile(file)
mockFileManager.delete(file)
mockFileManager.fileToUri(uri, file)
}
}
@ -1477,7 +1477,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel(state = DEFAULT_STATE, tempAttachmentFile = file)
coEvery {
mockFileManager.deleteFile(any())
mockFileManager.delete(any())
} just runs
viewModel.trySendAction(VaultItemAction.Common.NoAttachmentFileLocationReceive)
@ -1491,7 +1491,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
coVerify { mockFileManager.deleteFile(file) }
coVerify { mockFileManager.delete(file) }
}
}