mirror of
https://github.com/bitwarden/android.git
synced 2024-11-22 09:25:58 +03:00
[BIT-2275] Fix OutOfMemoryException when saving attachments (#1418)
This commit is contained in:
parent
a8f7c576fb
commit
51d65f602d
13 changed files with 274 additions and 112 deletions
|
@ -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>
|
||||
|
||||
/**
|
||||
|
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()),
|
||||
)
|
|
@ -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(
|
||||
|
|
|
@ -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 = "",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue