BIT-2129 Show descriptive error message when Send creation fails (#1186)

This commit is contained in:
Patrick Honkonen 2024-04-01 10:54:24 -04:00 committed by Álison Fernandes
parent 90ff2897f5
commit 1150f01666
14 changed files with 277 additions and 101 deletions

View file

@ -1,7 +1,7 @@
package com.x8bit.bitwarden.data.vault.datasource.network.api package com.x8bit.bitwarden.data.vault.datasource.network.api
import androidx.annotation.Keep import androidx.annotation.Keep
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendFileResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.SendJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import okhttp3.MultipartBody import okhttp3.MultipartBody
@ -28,7 +28,7 @@ interface SendsApi {
* Create a file send. * Create a file send.
*/ */
@POST("sends/file/v2") @POST("sends/file/v2")
suspend fun createFileSend(@Body body: SendJsonRequest): Result<SendFileResponseJson> suspend fun createFileSend(@Body body: SendJsonRequest): Result<CreateFileSendResponseJson>
/** /**
* Updates a send. * Updates a send.

View file

@ -0,0 +1,36 @@
package com.x8bit.bitwarden.data.vault.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Represents a response from create file send.
*/
sealed class CreateFileSendResponse {
/**
* Represents the response from a successful create file send request.
*
* @property createFileJsonResponse Response JSON received from create file send request.
*/
data class Success(
val createFileJsonResponse: CreateFileSendResponseJson,
) : CreateFileSendResponse()
/**
* Represents the json body of an invalid create request.
*
* @property message A general, user-displayable error message.
* @property validationErrors a map where each value is a list of error messages for each
* key. The values in the array should be used for display to the user, since the keys tend
* to come back as nonsense. (eg: empty string key)
*/
@Serializable
data class Invalid(
@SerialName("message")
override val message: String,
@SerialName("validationErrors")
override val validationErrors: Map<String, List<String>>?,
) : CreateFileSendResponse(), InvalidJsonResponse
}

View file

@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable
* Represents the JSON response from creating a new file send. * Represents the JSON response from creating a new file send.
*/ */
@Serializable @Serializable
data class SendFileResponseJson( data class CreateFileSendResponseJson(
@SerialName("url") @SerialName("url")
val url: String, val url: String,

View file

@ -0,0 +1,34 @@
package com.x8bit.bitwarden.data.vault.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Models a response from either "create text send" or "create file send" requests.
*/
sealed class CreateSendJsonResponse {
/**
* Represents a successful response from either "create text send" or "create file send"
* request.
*
* @property send The created send object.
*/
data class Success(val send: SyncResponseJson.Send) : CreateSendJsonResponse()
/**
* Represents the json body of an invalid create request.
*
* @property message A general, user-displayable error message.
* @property validationErrors a map where each value is a list of error messages for each
* key. The values in the array should be used for display to the user, since the keys tend
* to come back as nonsense. (eg: empty string key)
*/
@Serializable
data class Invalid(
@SerialName("message")
override val message: String,
@SerialName("validationErrors")
override val validationErrors: Map<String, List<String>>?,
) : CreateSendJsonResponse(), InvalidJsonResponse
}

View file

@ -0,0 +1,28 @@
package com.x8bit.bitwarden.data.vault.datasource.network.model
/**
* Represents the json body of an invalid send json request.
*/
sealed interface InvalidJsonResponse {
/**
* A general, user-displayable error message.
*/
val message: String
/**
* a map where each value is a list of error messages for each key.
* The values in the array should be used for display to the user, since the keys tend to come
* back as nonsense. (eg: empty string key)
*/
val validationErrors: Map<String, List<String>>?
/**
* Returns the first error message found in [validationErrors], or [message] if there are no
* [validationErrors] present.
*/
val firstValidationErrorMessage: String?
get() = validationErrors
?.flatMap { it.value }
?.first()
}

View file

@ -1,6 +1,8 @@
package com.x8bit.bitwarden.data.vault.datasource.network.service package com.x8bit.bitwarden.data.vault.datasource.network.service
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendFileResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponse
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateSendJsonResponse
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.SendJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson
@ -14,20 +16,20 @@ interface SendsService {
*/ */
suspend fun createTextSend( suspend fun createTextSend(
body: SendJsonRequest, body: SendJsonRequest,
): Result<SyncResponseJson.Send> ): Result<CreateSendJsonResponse>
/** /**
* Attempt to create a file send. * Attempt to create a file send.
*/ */
suspend fun createFileSend( suspend fun createFileSend(
body: SendJsonRequest, body: SendJsonRequest,
): Result<SendFileResponseJson> ): Result<CreateFileSendResponse>
/** /**
* Attempt to upload the given [encryptedFile] associated with the [sendFileResponse]. * Attempt to upload the given [encryptedFile] associated with the [sendFileResponse].
*/ */
suspend fun uploadFile( suspend fun uploadFile(
sendFileResponse: SendFileResponseJson, sendFileResponse: CreateFileSendResponseJson,
encryptedFile: ByteArray, encryptedFile: ByteArray,
): Result<SyncResponseJson.Send> ): Result<SyncResponseJson.Send>

View file

@ -5,8 +5,10 @@ import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenErr
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
import com.x8bit.bitwarden.data.vault.datasource.network.api.AzureApi import com.x8bit.bitwarden.data.vault.datasource.network.api.AzureApi
import com.x8bit.bitwarden.data.vault.datasource.network.api.SendsApi import com.x8bit.bitwarden.data.vault.datasource.network.api.SendsApi
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponse
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateSendJsonResponse
import com.x8bit.bitwarden.data.vault.datasource.network.model.FileUploadType import com.x8bit.bitwarden.data.vault.datasource.network.model.FileUploadType
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendFileResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.SendJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson
@ -28,11 +30,33 @@ class SendsServiceImpl(
private val clock: Clock, private val clock: Clock,
private val json: Json, private val json: Json,
) : SendsService { ) : SendsService {
override suspend fun createTextSend(body: SendJsonRequest): Result<SyncResponseJson.Send> = override suspend fun createTextSend(
body: SendJsonRequest,
): Result<CreateSendJsonResponse> =
sendsApi.createTextSend(body = body) sendsApi.createTextSend(body = body)
.map { CreateSendJsonResponse.Success(send = it) }
.recoverCatching { throwable ->
throwable.toBitwardenError()
.parseErrorBodyOrNull<CreateSendJsonResponse.Invalid>(
code = 400,
json = json,
)
?: throw throwable
}
override suspend fun createFileSend(body: SendJsonRequest): Result<SendFileResponseJson> = override suspend fun createFileSend(
body: SendJsonRequest,
): Result<CreateFileSendResponse> =
sendsApi.createFileSend(body = body) sendsApi.createFileSend(body = body)
.map { CreateFileSendResponse.Success(it) }
.recoverCatching { throwable ->
throwable.toBitwardenError()
.parseErrorBodyOrNull<CreateFileSendResponse.Invalid>(
code = 400,
json = json,
)
?: throw throwable
}
override suspend fun updateSend( override suspend fun updateSend(
sendId: String, sendId: String,
@ -55,7 +79,7 @@ class SendsServiceImpl(
} }
override suspend fun uploadFile( override suspend fun uploadFile(
sendFileResponse: SendFileResponseJson, sendFileResponse: CreateFileSendResponseJson,
encryptedFile: ByteArray, encryptedFile: ByteArray,
): Result<SyncResponseJson.Send> { ): Result<SyncResponseJson.Send> {
val send = sendFileResponse.sendResponse val send = sendFileResponse.sendResponse

View file

@ -10,6 +10,7 @@ import com.bitwarden.core.ExportFormat
import com.bitwarden.core.FolderView import com.bitwarden.core.FolderView
import com.bitwarden.core.InitOrgCryptoRequest import com.bitwarden.core.InitOrgCryptoRequest
import com.bitwarden.core.InitUserCryptoMethod import com.bitwarden.core.InitUserCryptoMethod
import com.bitwarden.core.Send
import com.bitwarden.core.SendType import com.bitwarden.core.SendType
import com.bitwarden.core.SendView import com.bitwarden.core.SendView
import com.bitwarden.crypto.Kdf import com.bitwarden.crypto.Kdf
@ -35,10 +36,13 @@ import com.x8bit.bitwarden.data.platform.repository.util.map
import com.x8bit.bitwarden.data.platform.repository.util.observeWhenSubscribedAndLoggedIn import com.x8bit.bitwarden.data.platform.repository.util.observeWhenSubscribedAndLoggedIn
import com.x8bit.bitwarden.data.platform.repository.util.updateToPendingOrLoading import com.x8bit.bitwarden.data.platform.repository.util.updateToPendingOrLoading
import com.x8bit.bitwarden.data.platform.util.asFailure import com.x8bit.bitwarden.data.platform.util.asFailure
import com.x8bit.bitwarden.data.platform.util.asSuccess
import com.x8bit.bitwarden.data.platform.util.flatMap import com.x8bit.bitwarden.data.platform.util.flatMap
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponse
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateSendJsonResponse
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson 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.UpdateCipherCollectionsJsonRequest
@ -980,11 +984,12 @@ class VaultRepositoryImpl(
) )
} }
@Suppress("ReturnCount")
override suspend fun createSend( override suspend fun createSend(
sendView: SendView, sendView: SendView,
fileUri: Uri?, fileUri: Uri?,
): CreateSendResult { ): CreateSendResult {
val userId = activeUserId ?: return CreateSendResult.Error val userId = activeUserId ?: return CreateSendResult.Error(message = null)
return vaultSdkSource return vaultSdkSource
.encryptSend( .encryptSend(
userId = userId, userId = userId,
@ -992,54 +997,33 @@ class VaultRepositoryImpl(
) )
.flatMap { send -> .flatMap { send ->
when (send.type) { when (send.type) {
SendType.TEXT -> { SendType.TEXT -> sendsService.createTextSend(send.toEncryptedNetworkSend())
sendsService.createTextSend(body = send.toEncryptedNetworkSend()) SendType.FILE -> createFileSend(fileUri, userId, send)
}
}
.map { createSendResponse ->
when (createSendResponse) {
is CreateSendJsonResponse.Invalid -> {
return CreateSendResult.Error(
message = createSendResponse.firstValidationErrorMessage,
)
} }
SendType.FILE -> { is CreateSendJsonResponse.Success -> {
val uri = fileUri ?: return@flatMap IllegalArgumentException(
"File URI must be present to create a File Send.",
)
.asFailure()
fileManager
.uriToByteArray(fileUri = uri)
.flatMap {
vaultSdkSource.encryptBuffer(
userId = userId,
send = send,
fileBuffer = it,
)
}
.flatMap { encryptedFile ->
sendsService
.createFileSend(
body = send.toEncryptedNetworkSend(
fileLength = encryptedFile.size,
),
)
.flatMap { sendFileResponse ->
sendsService.uploadFile(
sendFileResponse = sendFileResponse,
encryptedFile = encryptedFile,
)
}
}
}
}
}
.onSuccess {
// Save the send immediately, regardless of whether the decrypt succeeds // Save the send immediately, regardless of whether the decrypt succeeds
vaultDiskSource.saveSend(userId = userId, send = it) vaultDiskSource.saveSend(userId = userId, send = createSendResponse.send)
createSendResponse
} }
.flatMap { }
}
.flatMap { createSendSuccessResponse ->
vaultSdkSource.decryptSend( vaultSdkSource.decryptSend(
userId = userId, userId = userId,
send = it.toEncryptedSdkSend(), send = createSendSuccessResponse.send.toEncryptedSdkSend(),
) )
} }
.fold( .fold(
onFailure = { CreateSendResult.Error }, onFailure = { CreateSendResult.Error(message = null) },
onSuccess = { CreateSendResult.Success(it) }, onSuccess = { CreateSendResult.Success(it) },
) )
} }
@ -1594,6 +1578,56 @@ class VaultRepositoryImpl(
) )
} }
private suspend fun createFileSend(
uri: Uri?,
userId: String,
send: Send,
): Result<CreateSendJsonResponse> {
uri ?: return IllegalArgumentException(
"File URI must be present to create a File Send.",
)
.asFailure()
return fileManager
.uriToByteArray(fileUri = uri)
.flatMap {
vaultSdkSource.encryptBuffer(
userId = userId,
send = send,
fileBuffer = it,
)
}
.flatMap { encryptedFile ->
sendsService
.createFileSend(
body = send.toEncryptedNetworkSend(
fileLength = encryptedFile.size,
),
)
.flatMap { sendFileResponse ->
when (sendFileResponse) {
is CreateFileSendResponse.Invalid -> {
CreateSendJsonResponse
.Invalid(
message = sendFileResponse.message,
validationErrors = sendFileResponse.validationErrors,
)
.asSuccess()
}
is CreateFileSendResponse.Success -> {
sendsService
.uploadFile(
sendFileResponse = sendFileResponse.createFileJsonResponse,
encryptedFile = encryptedFile,
)
.map { CreateSendJsonResponse.Success(it) }
}
}
}
}
}
/** /**
* Deletes the send specified by [syncSendDeleteData] from disk. * Deletes the send specified by [syncSendDeleteData] from disk.
*/ */

View file

@ -15,5 +15,5 @@ sealed class CreateSendResult {
/** /**
* Generic error while creating a send. * Generic error while creating a send.
*/ */
data object Error : CreateSendResult() data class Error(val message: String?) : CreateSendResult()
} }

View file

@ -184,12 +184,13 @@ class AddSendViewModel @Inject constructor(
action: AddSendAction.Internal.CreateSendResultReceive, action: AddSendAction.Internal.CreateSendResultReceive,
) { ) {
when (val result = action.result) { when (val result = action.result) {
CreateSendResult.Error -> { is CreateSendResult.Error -> {
mutableStateFlow.update { mutableStateFlow.update {
it.copy( it.copy(
dialogState = AddSendState.DialogState.Error( dialogState = AddSendState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(), title = R.string.an_error_has_occurred.asText(),
message = R.string.generic_error_message.asText(), message = result.message?.asText()
?: R.string.generic_error_message.asText(),
), ),
) )
} }

View file

@ -2,6 +2,13 @@ package com.x8bit.bitwarden.data.vault.datasource.network.model
import java.time.ZonedDateTime import java.time.ZonedDateTime
fun createMockFileSendResponseJson(number: Int, type: SendTypeJson = SendTypeJson.FILE) =
CreateFileSendResponseJson(
url = "www.test.com",
fileUploadType = FileUploadType.AZURE,
sendResponse = createMockSend(number = number, type = type),
)
fun createMockSend( fun createMockSend(
number: Int, number: Int,
type: SendTypeJson = SendTypeJson.FILE, type: SendTypeJson = SendTypeJson.FILE,

View file

@ -4,10 +4,11 @@ import android.net.Uri
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
import com.x8bit.bitwarden.data.vault.datasource.network.api.AzureApi import com.x8bit.bitwarden.data.vault.datasource.network.api.AzureApi
import com.x8bit.bitwarden.data.vault.datasource.network.api.SendsApi import com.x8bit.bitwarden.data.vault.datasource.network.api.SendsApi
import com.x8bit.bitwarden.data.vault.datasource.network.model.FileUploadType import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponse
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendFileResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateSendJsonResponse
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendTypeJson import com.x8bit.bitwarden.data.vault.datasource.network.model.SendTypeJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockFileSendResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSend import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSend
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSendJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSendJsonRequest
import io.mockk.every import io.mockk.every
@ -52,16 +53,18 @@ class SendsServiceTest : BaseServiceTest() {
@Test @Test
fun `createFileSend should return the correct response`() = runTest { fun `createFileSend should return the correct response`() = runTest {
val response = SendFileResponseJson( val sendFileResponse = CreateFileSendResponse.Success(
url = "www.test.com", createFileJsonResponse = createMockFileSendResponseJson(number = 1),
fileUploadType = FileUploadType.AZURE,
sendResponse = createMockSend(number = 1, type = SendTypeJson.FILE),
) )
server.enqueue(MockResponse().setBody(CREATE_FILE_SEND_SUCCESS_JSON)) server.enqueue(MockResponse().setBody(CREATE_FILE_SEND_SUCCESS_JSON))
val result = sendsService.createFileSend( val result = sendsService.createFileSend(
body = createMockSendJsonRequest(number = 1, type = SendTypeJson.FILE), body = createMockSendJsonRequest(number = 1, type = SendTypeJson.FILE),
) )
assertEquals(response, result.getOrThrow())
assertEquals(
sendFileResponse,
result.getOrThrow(),
)
} }
@Test @Test
@ -71,7 +74,7 @@ class SendsServiceTest : BaseServiceTest() {
body = createMockSendJsonRequest(number = 1, type = SendTypeJson.TEXT), body = createMockSendJsonRequest(number = 1, type = SendTypeJson.TEXT),
) )
assertEquals( assertEquals(
createMockSend(number = 1), CreateSendJsonResponse.Success(createMockSend(number = 1)),
result.getOrThrow(), result.getOrThrow(),
) )
} }
@ -113,13 +116,9 @@ class SendsServiceTest : BaseServiceTest() {
fun `uploadFile with Azure uploadFile success should return send`() = runTest { fun `uploadFile with Azure uploadFile success should return send`() = runTest {
val url = "www.test.com" val url = "www.test.com"
setupMockUri(url = url, queryParams = mapOf("sv" to "2024-04-03")) setupMockUri(url = url, queryParams = mapOf("sv" to "2024-04-03"))
val mockSend = createMockSend(number = 1, type = SendTypeJson.FILE) val sendFileResponse = createMockFileSendResponseJson(number = 1)
val sendFileResponse = SendFileResponseJson(
url = url,
fileUploadType = FileUploadType.AZURE,
sendResponse = mockSend,
)
val encryptedFile = byteArrayOf() val encryptedFile = byteArrayOf()
server.enqueue(MockResponse().setResponseCode(201)) server.enqueue(MockResponse().setResponseCode(201))
val result = sendsService.uploadFile( val result = sendsService.uploadFile(
@ -127,17 +126,14 @@ class SendsServiceTest : BaseServiceTest() {
encryptedFile = encryptedFile, encryptedFile = encryptedFile,
) )
assertEquals(mockSend, result.getOrThrow()) assertEquals(sendFileResponse.sendResponse, result.getOrThrow())
} }
@Test @Test
fun `uploadFile with Direct uploadFile success should return send`() = runTest { fun `uploadFile with Direct uploadFile success should return send`() = runTest {
val mockSend = createMockSend(number = 1, type = SendTypeJson.FILE) val url = "www.test.com"
val sendFileResponse = SendFileResponseJson( setupMockUri(url = url, queryParams = mapOf("sv" to "2024-04-03"))
url = "www.test.com", val sendFileResponse = createMockFileSendResponseJson(number = 1)
fileUploadType = FileUploadType.DIRECT,
sendResponse = mockSend,
)
val encryptedFile = byteArrayOf() val encryptedFile = byteArrayOf()
server.enqueue(MockResponse().setResponseCode(201)) server.enqueue(MockResponse().setResponseCode(201))
@ -146,7 +142,7 @@ class SendsServiceTest : BaseServiceTest() {
encryptedFile = encryptedFile, encryptedFile = encryptedFile,
) )
assertEquals(mockSend, result.getOrThrow()) assertEquals(sendFileResponse.sendResponse, result.getOrThrow())
} }
@Test @Test

View file

@ -39,9 +39,9 @@ import com.x8bit.bitwarden.data.platform.util.asSuccess
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.FileUploadType import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponse
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateSendJsonResponse
import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest
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.SendTypeJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
@ -55,6 +55,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipher
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipherJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipherJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCollection import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCollection
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockDomains import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockDomains
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockFileSendResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockFolder import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockFolder
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganization import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganization
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganizationKeys import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganizationKeys
@ -2430,7 +2431,7 @@ class VaultRepositoryTest {
) )
assertEquals( assertEquals(
CreateSendResult.Error, CreateSendResult.Error(message = null),
result, result,
) )
} }
@ -2447,7 +2448,7 @@ class VaultRepositoryTest {
val result = vaultRepository.createSend(sendView = mockSendView, fileUri = null) val result = vaultRepository.createSend(sendView = mockSendView, fileUri = null)
assertEquals(CreateSendResult.Error, result) assertEquals(CreateSendResult.Error(message = null), result)
} }
@Test @Test
@ -2469,7 +2470,7 @@ class VaultRepositoryTest {
val result = vaultRepository.createSend(sendView = mockSendView, fileUri = null) val result = vaultRepository.createSend(sendView = mockSendView, fileUri = null)
assertEquals(CreateSendResult.Error, result) assertEquals(CreateSendResult.Error(IllegalStateException().message), result)
} }
@Suppress("MaxLineLength") @Suppress("MaxLineLength")
@ -2482,6 +2483,7 @@ class VaultRepositoryTest {
val mockSdkSend = createMockSdkSend(number = 1, type = SendType.TEXT) val mockSdkSend = createMockSdkSend(number = 1, type = SendType.TEXT)
val mockSend = createMockSend(number = 1, type = SendTypeJson.TEXT) val mockSend = createMockSend(number = 1, type = SendTypeJson.TEXT)
val mockSendViewResult = createMockSendView(number = 2) val mockSendViewResult = createMockSendView(number = 2)
val sendTextResponse = CreateSendJsonResponse.Success(send = mockSend)
coEvery { coEvery {
vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView) vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView)
} returns mockSdkSend.asSuccess() } returns mockSdkSend.asSuccess()
@ -2490,7 +2492,7 @@ class VaultRepositoryTest {
body = createMockSendJsonRequest(number = 1, type = SendTypeJson.TEXT) body = createMockSendJsonRequest(number = 1, type = SendTypeJson.TEXT)
.copy(fileLength = null), .copy(fileLength = null),
) )
} returns mockSend.asSuccess() } returns sendTextResponse.asSuccess()
coEvery { vaultDiskSource.saveSend(userId, mockSend) } just runs coEvery { vaultDiskSource.saveSend(userId, mockSend) } just runs
coEvery { coEvery {
vaultSdkSource.decryptSend(userId, mockSdkSend) vaultSdkSource.decryptSend(userId, mockSdkSend)
@ -2529,7 +2531,7 @@ class VaultRepositoryTest {
val result = vaultRepository.createSend(sendView = mockSendView, fileUri = uri) val result = vaultRepository.createSend(sendView = mockSendView, fileUri = uri)
assertEquals(CreateSendResult.Error, result) assertEquals(CreateSendResult.Error(IllegalStateException().message), result)
} }
@Test @Test
@ -2544,14 +2546,18 @@ class VaultRepositoryTest {
val mockSdkSend = createMockSdkSend(number = 1) val mockSdkSend = createMockSdkSend(number = 1)
val byteArray = byteArrayOf(1) val byteArray = byteArrayOf(1)
val encryptedByteArray = byteArrayOf(2) val encryptedByteArray = byteArrayOf(2)
val sendFileResponse = SendFileResponseJson( val sendFileResponse = CreateFileSendResponse.Success(
url = url, createMockFileSendResponseJson(
fileUploadType = FileUploadType.AZURE, number = 1,
sendResponse = createMockSend(number = 1, type = SendTypeJson.FILE), type = SendTypeJson.FILE,
),
) )
coEvery { coEvery {
vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView) vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView)
} returns mockSdkSend.asSuccess() } returns mockSdkSend.asSuccess()
coEvery {
vaultSdkSource.decryptSend(userId, mockSdkSend)
} returns mockSendView.asSuccess()
coEvery { fileManager.uriToByteArray(any()) } returns byteArray.asSuccess() coEvery { fileManager.uriToByteArray(any()) } returns byteArray.asSuccess()
coEvery { coEvery {
vaultSdkSource.encryptBuffer( vaultSdkSource.encryptBuffer(
@ -2560,19 +2566,25 @@ class VaultRepositoryTest {
fileBuffer = byteArray, fileBuffer = byteArray,
) )
} returns encryptedByteArray.asSuccess() } returns encryptedByteArray.asSuccess()
coEvery {
vaultDiskSource.saveSend(
userId,
sendFileResponse.createFileJsonResponse.sendResponse,
)
} just runs
coEvery { coEvery {
sendsService.createFileSend(body = createMockSendJsonRequest(number = 1)) sendsService.createFileSend(body = createMockSendJsonRequest(number = 1))
} returns sendFileResponse.asSuccess() } returns sendFileResponse.asSuccess()
coEvery { coEvery {
sendsService.uploadFile( sendsService.uploadFile(
sendFileResponse = sendFileResponse, sendFileResponse = sendFileResponse.createFileJsonResponse,
encryptedFile = encryptedByteArray, encryptedFile = encryptedByteArray,
) )
} returns Throwable("Fail").asFailure() } returns Throwable().asFailure()
val result = vaultRepository.createSend(sendView = mockSendView, fileUri = uri) val result = vaultRepository.createSend(sendView = mockSendView, fileUri = uri)
assertEquals(CreateSendResult.Error, result) assertEquals(CreateSendResult.Error(null), result)
} }
@Test @Test
@ -2588,11 +2600,11 @@ class VaultRepositoryTest {
coEvery { coEvery {
vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView) vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView)
} returns mockSdkSend.asSuccess() } returns mockSdkSend.asSuccess()
coEvery { fileManager.uriToByteArray(any()) } returns Throwable("Fail").asFailure() coEvery { fileManager.uriToByteArray(any()) } returns Throwable().asFailure()
val result = vaultRepository.createSend(sendView = mockSendView, fileUri = uri) val result = vaultRepository.createSend(sendView = mockSendView, fileUri = uri)
assertEquals(CreateSendResult.Error, result) assertEquals(CreateSendResult.Error(message = null), result)
} }
@Test @Test
@ -2607,11 +2619,8 @@ class VaultRepositoryTest {
val mockSdkSend = createMockSdkSend(number = 1) val mockSdkSend = createMockSdkSend(number = 1)
val byteArray = byteArrayOf(1) val byteArray = byteArrayOf(1)
val encryptedByteArray = byteArrayOf(2) val encryptedByteArray = byteArrayOf(2)
val sendResponse = createMockSend(number = 1) val sendFileResponse = CreateFileSendResponse.Success(
val sendFileResponse = SendFileResponseJson( createMockFileSendResponseJson(number = 1),
url = url,
fileUploadType = FileUploadType.AZURE,
sendResponse = sendResponse,
) )
val mockSendViewResult = createMockSendView(number = 1) val mockSendViewResult = createMockSendView(number = 1)
coEvery { coEvery {
@ -2630,11 +2639,16 @@ class VaultRepositoryTest {
} returns sendFileResponse.asSuccess() } returns sendFileResponse.asSuccess()
coEvery { coEvery {
sendsService.uploadFile( sendsService.uploadFile(
sendFileResponse = sendFileResponse, sendFileResponse = sendFileResponse.createFileJsonResponse,
encryptedFile = encryptedByteArray, encryptedFile = encryptedByteArray,
) )
} returns sendResponse.asSuccess() } returns sendFileResponse.createFileJsonResponse.sendResponse.asSuccess()
coEvery { vaultDiskSource.saveSend(userId, sendResponse) } just runs coEvery {
vaultDiskSource.saveSend(
userId,
sendFileResponse.createFileJsonResponse.sendResponse,
)
} just runs
coEvery { coEvery {
vaultSdkSource.decryptSend(userId, mockSdkSend) vaultSdkSource.decryptSend(userId, mockSdkSend)
} returns mockSendViewResult.asSuccess() } returns mockSendViewResult.asSuccess()

View file

@ -259,7 +259,7 @@ class AddSendViewModelTest : BaseViewModelTest() {
every { viewState.toSendView(clock) } returns mockSendView every { viewState.toSendView(clock) } returns mockSendView
coEvery { coEvery {
vaultRepository.createSend(sendView = mockSendView, fileUri = null) vaultRepository.createSend(sendView = mockSendView, fileUri = null)
} returns CreateSendResult.Error } returns CreateSendResult.Error("Fail")
val viewModel = createViewModel(initialState) val viewModel = createViewModel(initialState)
viewModel.stateFlow.test { viewModel.stateFlow.test {
@ -277,7 +277,7 @@ class AddSendViewModelTest : BaseViewModelTest() {
initialState.copy( initialState.copy(
dialogState = AddSendState.DialogState.Error( dialogState = AddSendState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(), title = R.string.an_error_has_occurred.asText(),
message = R.string.generic_error_message.asText(), message = "Fail".asText(),
), ),
), ),
awaitItem(), awaitItem(),