mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 10:48:47 +03:00
Add underlying support for file sends (#646)
This commit is contained in:
parent
dbbb9f6587
commit
cd236f183f
24 changed files with 676 additions and 46 deletions
|
@ -0,0 +1,25 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.api
|
||||
|
||||
import okhttp3.RequestBody
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Url
|
||||
|
||||
/**
|
||||
* Defines raw calls to the Azure API without any authentication applied.
|
||||
*/
|
||||
interface AzureApi {
|
||||
/**
|
||||
* Attempts to upload an encrypted file to Azure.
|
||||
*/
|
||||
@PUT
|
||||
@Headers("x-ms-blob-type: BlockBlob")
|
||||
suspend fun uploadAzureBlob(
|
||||
@Url url: String,
|
||||
@Header("x-ms-date") date: String,
|
||||
@Header("x-ms-version") version: String?,
|
||||
@Body body: RequestBody,
|
||||
): Result<Unit>
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.api
|
||||
|
||||
import androidx.annotation.Keep
|
||||
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.SyncResponseJson
|
||||
import okhttp3.MultipartBody
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.POST
|
||||
|
@ -21,6 +23,12 @@ interface SendsApi {
|
|||
@POST("sends")
|
||||
suspend fun createSend(@Body body: SendJsonRequest): Result<SyncResponseJson.Send>
|
||||
|
||||
/**
|
||||
* Create a file send.
|
||||
*/
|
||||
@POST("sends/file/v2")
|
||||
suspend fun createFileSend(@Body body: SendJsonRequest): Result<SendFileResponseJson>
|
||||
|
||||
/**
|
||||
* Updates a send.
|
||||
*/
|
||||
|
@ -30,6 +38,16 @@ interface SendsApi {
|
|||
@Body body: SendJsonRequest,
|
||||
): Result<SyncResponseJson.Send>
|
||||
|
||||
/**
|
||||
* Uploads the file associated with a send.
|
||||
*/
|
||||
@POST("sends/{sendId}/file/{fileId}")
|
||||
suspend fun uploadFile(
|
||||
@Path("sendId") sendId: String,
|
||||
@Path("fileId") fileId: String,
|
||||
@Body body: MultipartBody,
|
||||
): Result<Unit>
|
||||
|
||||
/**
|
||||
* Deletes a send.
|
||||
*/
|
||||
|
|
|
@ -13,6 +13,7 @@ import dagger.hilt.InstallIn
|
|||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.json.Json
|
||||
import retrofit2.create
|
||||
import java.time.Clock
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
|
@ -37,9 +38,17 @@ object VaultNetworkModule {
|
|||
fun provideSendsService(
|
||||
retrofits: Retrofits,
|
||||
json: Json,
|
||||
clock: Clock,
|
||||
): SendsService = SendsServiceImpl(
|
||||
azureApi = retrofits
|
||||
.staticRetrofitBuilder
|
||||
// This URL will be overridden dynamically
|
||||
.baseUrl("https://www.bitwaredn.com")
|
||||
.build()
|
||||
.create(),
|
||||
sendsApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
json = json,
|
||||
clock = clock,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.serializer.BaseEnumeratedIntSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents the JSON response from creating a new file send.
|
||||
*/
|
||||
@Serializable
|
||||
data class SendFileResponseJson(
|
||||
@SerialName("url")
|
||||
val url: String,
|
||||
|
||||
@SerialName("fileUploadType")
|
||||
val fileUploadType: FileUploadType,
|
||||
|
||||
@SerialName("sendResponse")
|
||||
val sendResponse: SyncResponseJson.Send,
|
||||
) {
|
||||
/**
|
||||
* Represents the type of file upload that should be used.
|
||||
*/
|
||||
@Serializable(FileUploadTypeSerializer::class)
|
||||
enum class FileUploadType {
|
||||
@SerialName("0")
|
||||
DIRECT,
|
||||
|
||||
@SerialName("1")
|
||||
AZURE,
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
private class FileUploadTypeSerializer :
|
||||
BaseEnumeratedIntSerializer<SendFileResponseJson.FileUploadType>(
|
||||
SendFileResponseJson.FileUploadType.entries.toTypedArray(),
|
||||
)
|
|
@ -16,6 +16,7 @@ import java.time.ZonedDateTime
|
|||
* @property expirationDate The date in which the send will expire (nullable).
|
||||
* @property deletionDate The date in which the send will be deleted.
|
||||
* @property file The file associated with this send (nullable).
|
||||
* @property fileLength The length of the file in bytes (nullable).
|
||||
* @property text The text associated with this send (nullable).
|
||||
* @property password The password protecting this send (nullable).
|
||||
* @property isDisabled Indicate if this send is disabled.
|
||||
|
@ -46,6 +47,9 @@ data class SendJsonRequest(
|
|||
@Contextual
|
||||
val deletionDate: ZonedDateTime,
|
||||
|
||||
@SerialName("fileLength")
|
||||
val fileLength: Int?,
|
||||
|
||||
@SerialName("file")
|
||||
val file: SyncResponseJson.Send.File?,
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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.SendJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson
|
||||
|
@ -15,6 +16,21 @@ interface SendsService {
|
|||
body: SendJsonRequest,
|
||||
): Result<SyncResponseJson.Send>
|
||||
|
||||
/**
|
||||
* Attempt to create a file send.
|
||||
*/
|
||||
suspend fun createFileSend(
|
||||
body: SendJsonRequest,
|
||||
): Result<SendFileResponseJson>
|
||||
|
||||
/**
|
||||
* Attempt to upload the given [encryptedFile] associated with the [sendFileResponse].
|
||||
*/
|
||||
suspend fun uploadFile(
|
||||
sendFileResponse: SendFileResponseJson,
|
||||
encryptedFile: ByteArray,
|
||||
): Result<SyncResponseJson.Send>
|
||||
|
||||
/**
|
||||
* Attempt to update a send.
|
||||
*/
|
||||
|
|
|
@ -1,23 +1,38 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.service
|
||||
|
||||
import androidx.core.net.toUri
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
|
||||
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.SendsApi
|
||||
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.SyncResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.time.Clock
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
/**
|
||||
* Default implementation of the [SendsService].
|
||||
*/
|
||||
class SendsServiceImpl(
|
||||
private val azureApi: AzureApi,
|
||||
private val sendsApi: SendsApi,
|
||||
private val clock: Clock,
|
||||
private val json: Json,
|
||||
) : SendsService {
|
||||
override suspend fun createSend(body: SendJsonRequest): Result<SyncResponseJson.Send> =
|
||||
sendsApi.createSend(body = body)
|
||||
|
||||
override suspend fun createFileSend(body: SendJsonRequest): Result<SendFileResponseJson> =
|
||||
sendsApi.createFileSend(body = body)
|
||||
|
||||
override suspend fun updateSend(
|
||||
sendId: String,
|
||||
body: SendJsonRequest,
|
||||
|
@ -38,6 +53,48 @@ class SendsServiceImpl(
|
|||
?: throw throwable
|
||||
}
|
||||
|
||||
override suspend fun uploadFile(
|
||||
sendFileResponse: SendFileResponseJson,
|
||||
encryptedFile: ByteArray,
|
||||
): Result<SyncResponseJson.Send> {
|
||||
val send = sendFileResponse.sendResponse
|
||||
return when (sendFileResponse.fileUploadType) {
|
||||
SendFileResponseJson.FileUploadType.DIRECT -> {
|
||||
sendsApi.uploadFile(
|
||||
sendId = requireNotNull(send.id),
|
||||
fileId = requireNotNull(send.file?.id),
|
||||
body = MultipartBody
|
||||
.Builder(
|
||||
boundary = "--BWMobileFormBoundary${clock.instant().toEpochMilli()}",
|
||||
)
|
||||
.addPart(
|
||||
part = MultipartBody.Part.createFormData(
|
||||
body = encryptedFile.toRequestBody(
|
||||
contentType = "application/octet-stream".toMediaType(),
|
||||
),
|
||||
name = "data",
|
||||
filename = send.file?.fileName,
|
||||
),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
SendFileResponseJson.FileUploadType.AZURE -> {
|
||||
azureApi.uploadAzureBlob(
|
||||
url = sendFileResponse.url,
|
||||
date = DateTimeFormatter
|
||||
.RFC_1123_DATE_TIME
|
||||
.format(ZonedDateTime.ofInstant(clock.instant(), ZoneOffset.UTC)),
|
||||
version = sendFileResponse.url.toUri().getQueryParameter("sv"),
|
||||
body = encryptedFile.toRequestBody(),
|
||||
)
|
||||
}
|
||||
}
|
||||
.onFailure { sendsApi.deleteSend(send.id) }
|
||||
.map { send }
|
||||
}
|
||||
|
||||
override suspend fun deleteSend(sendId: String): Result<Unit> =
|
||||
sendsApi.deleteSend(sendId = sendId)
|
||||
|
||||
|
|
|
@ -169,6 +169,19 @@ interface VaultSdkSource {
|
|||
sendView: SendView,
|
||||
): Result<Send>
|
||||
|
||||
/**
|
||||
* Encrypts a [ByteArray] file buffer for the user with the given [userId], returning an
|
||||
* encrypted [ByteArray] wrapped in a [Result].
|
||||
*
|
||||
* This should only be called after a successful call to [initializeCrypto] for the associated
|
||||
* user.
|
||||
*/
|
||||
suspend fun encryptBuffer(
|
||||
userId: String,
|
||||
send: Send,
|
||||
fileBuffer: ByteArray,
|
||||
): Result<ByteArray>
|
||||
|
||||
/**
|
||||
* Decrypts a [Send] for the user with the given [userId], returning a [SendView] wrapped in a
|
||||
* [Result].
|
||||
|
|
|
@ -104,6 +104,21 @@ class VaultSdkSourceImpl(
|
|||
.encrypt(sendView)
|
||||
}
|
||||
|
||||
override suspend fun encryptBuffer(
|
||||
userId: String,
|
||||
send: Send,
|
||||
fileBuffer: ByteArray,
|
||||
): Result<ByteArray> =
|
||||
runCatching {
|
||||
getClient(userId = userId)
|
||||
.vault()
|
||||
.sends()
|
||||
.encryptBuffer(
|
||||
send = send,
|
||||
buffer = fileBuffer,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun encryptCipher(
|
||||
userId: String,
|
||||
cipherView: CipherView,
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package com.x8bit.bitwarden.data.vault.manager
|
||||
|
||||
import android.net.Uri
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
|
||||
/**
|
||||
* Manages reading files.
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
interface FileManager {
|
||||
|
||||
/**
|
||||
* Reads the [fileUri] into memory and returns the raw [ByteArray]
|
||||
*/
|
||||
fun uriToByteArray(fileUri: Uri): ByteArray
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package com.x8bit.bitwarden.data.vault.manager
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
/**
|
||||
* The buffer size to be used when reading from an input stream.
|
||||
*/
|
||||
private const val BUFFER_SIZE: Int = 1024
|
||||
|
||||
/**
|
||||
* The default implementation of the [FileManager] interface.
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
class FileManagerImpl(
|
||||
private val context: Context,
|
||||
) : FileManager {
|
||||
|
||||
override fun uriToByteArray(fileUri: Uri): ByteArray =
|
||||
context
|
||||
.contentResolver
|
||||
.openInputStream(fileUri)
|
||||
?.use { inputStream ->
|
||||
ByteArrayOutputStream().use { outputStream ->
|
||||
val buffer = ByteArray(BUFFER_SIZE)
|
||||
var length: Int
|
||||
while (inputStream.read(buffer).also { length = it } != -1) {
|
||||
outputStream.write(buffer, 0, length)
|
||||
}
|
||||
outputStream.toByteArray()
|
||||
}
|
||||
}
|
||||
?: byteArrayOf()
|
||||
}
|
|
@ -1,16 +1,20 @@
|
|||
package com.x8bit.bitwarden.data.vault.manager.di
|
||||
|
||||
import android.content.Context
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.manager.FileManager
|
||||
import com.x8bit.bitwarden.data.vault.manager.FileManagerImpl
|
||||
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
||||
import com.x8bit.bitwarden.data.vault.manager.VaultLockManagerImpl
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
@ -21,6 +25,12 @@ import javax.inject.Singleton
|
|||
@InstallIn(SingletonComponent::class)
|
||||
object VaultManagerModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFileManager(
|
||||
@ApplicationContext context: Context,
|
||||
): FileManager = FileManagerImpl(context)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideVaultLockManager(
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository
|
||||
|
||||
import android.net.Uri
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.CollectionView
|
||||
import com.bitwarden.core.FolderView
|
||||
import com.bitwarden.core.SendType
|
||||
import com.bitwarden.core.SendView
|
||||
import com.bitwarden.crypto.Kdf
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
|
@ -159,9 +161,10 @@ interface VaultRepository : VaultLockManager {
|
|||
): UpdateCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to create a send.
|
||||
* Attempt to create a send. The [fileUri] _must_ be present when the given [SendView] has a
|
||||
* [SendView.type] of [SendType.FILE].
|
||||
*/
|
||||
suspend fun createSend(sendView: SendView): CreateSendResult
|
||||
suspend fun createSend(sendView: SendView, fileUri: Uri?): CreateSendResult
|
||||
|
||||
/**
|
||||
* Attempt to update a send.
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository
|
||||
|
||||
import android.net.Uri
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.CollectionView
|
||||
import com.bitwarden.core.FolderView
|
||||
import com.bitwarden.core.InitOrgCryptoRequest
|
||||
import com.bitwarden.core.InitUserCryptoMethod
|
||||
import com.bitwarden.core.SendType
|
||||
import com.bitwarden.core.SendView
|
||||
import com.bitwarden.crypto.Kdf
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
|
@ -18,6 +20,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.combineDataStates
|
|||
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.updateToPendingOrLoading
|
||||
import com.x8bit.bitwarden.data.platform.util.asFailure
|
||||
import com.x8bit.bitwarden.data.platform.util.flatMap
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
|
@ -27,6 +30,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
|
|||
import com.x8bit.bitwarden.data.vault.datasource.network.service.SendsService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.manager.FileManager
|
||||
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
||||
|
@ -80,6 +84,7 @@ class VaultRepositoryImpl(
|
|||
private val vaultDiskSource: VaultDiskSource,
|
||||
private val vaultSdkSource: VaultSdkSource,
|
||||
private val authDiskSource: AuthDiskSource,
|
||||
private val fileManager: FileManager,
|
||||
private val vaultLockManager: VaultLockManager,
|
||||
dispatcherManager: DispatcherManager,
|
||||
) : VaultRepository,
|
||||
|
@ -415,14 +420,50 @@ class VaultRepositoryImpl(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun createSend(sendView: SendView): CreateSendResult {
|
||||
override suspend fun createSend(
|
||||
sendView: SendView,
|
||||
fileUri: Uri?,
|
||||
): CreateSendResult {
|
||||
val userId = requireNotNull(activeUserId)
|
||||
return vaultSdkSource
|
||||
.encryptSend(
|
||||
userId = userId,
|
||||
sendView = sendView,
|
||||
)
|
||||
.flatMap { send -> sendsService.createSend(body = send.toEncryptedNetworkSend()) }
|
||||
.flatMap { send ->
|
||||
when (send.type) {
|
||||
SendType.TEXT -> {
|
||||
sendsService.createSend(body = send.toEncryptedNetworkSend())
|
||||
}
|
||||
|
||||
SendType.FILE -> {
|
||||
val uri = fileUri ?: return@flatMap IllegalArgumentException(
|
||||
"File URI must be present to create a File Send.",
|
||||
)
|
||||
.asFailure()
|
||||
vaultSdkSource
|
||||
.encryptBuffer(
|
||||
userId = userId,
|
||||
send = send,
|
||||
fileBuffer = fileManager.uriToByteArray(fileUri = uri),
|
||||
)
|
||||
.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
|
||||
vaultDiskSource.saveSend(userId = userId, send = it)
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
|
|||
import com.x8bit.bitwarden.data.vault.datasource.network.service.SendsService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.manager.FileManager
|
||||
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepositoryImpl
|
||||
|
@ -32,6 +33,7 @@ object VaultRepositoryModule {
|
|||
vaultDiskSource: VaultDiskSource,
|
||||
vaultSdkSource: VaultSdkSource,
|
||||
authDiskSource: AuthDiskSource,
|
||||
fileManager: FileManager,
|
||||
vaultLockManager: VaultLockManager,
|
||||
dispatcherManager: DispatcherManager,
|
||||
): VaultRepository = VaultRepositoryImpl(
|
||||
|
@ -41,6 +43,7 @@ object VaultRepositoryModule {
|
|||
vaultDiskSource = vaultDiskSource,
|
||||
vaultSdkSource = vaultSdkSource,
|
||||
authDiskSource = authDiskSource,
|
||||
fileManager = fileManager,
|
||||
vaultLockManager = vaultLockManager,
|
||||
dispatcherManager = dispatcherManager,
|
||||
)
|
||||
|
|
|
@ -13,7 +13,7 @@ import java.time.ZonedDateTime
|
|||
/**
|
||||
* Converts a Bitwarden SDK [Send] object to a corresponding [SyncResponseJson.Send] object.
|
||||
*/
|
||||
fun Send.toEncryptedNetworkSend(): SendJsonRequest =
|
||||
fun Send.toEncryptedNetworkSend(fileLength: Int? = null): SendJsonRequest =
|
||||
SendJsonRequest(
|
||||
type = type.toNetworkSendType(),
|
||||
name = name,
|
||||
|
@ -22,6 +22,7 @@ fun Send.toEncryptedNetworkSend(): SendJsonRequest =
|
|||
maxAccessCount = maxAccessCount?.toInt(),
|
||||
expirationDate = expirationDate?.let { ZonedDateTime.ofInstant(it, ZoneOffset.UTC) },
|
||||
deletionDate = ZonedDateTime.ofInstant(deletionDate, ZoneOffset.UTC),
|
||||
fileLength = fileLength,
|
||||
file = file?.toNetworkSendFile(),
|
||||
text = text?.toNetworkSendText(),
|
||||
password = password,
|
||||
|
|
|
@ -452,7 +452,10 @@ class AddSendViewModel @Inject constructor(
|
|||
viewModelScope.launch {
|
||||
when (val addSendType = state.addSendType) {
|
||||
AddSendType.AddItem -> {
|
||||
val result = vaultRepo.createSend(content.toSendView(clock))
|
||||
val result = vaultRepo.createSend(
|
||||
sendView = content.toSendView(clock),
|
||||
fileUri = null,
|
||||
)
|
||||
sendAction(AddSendAction.Internal.CreateSendResultReceive(result))
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ fun createMockSendJsonRequest(
|
|||
maxAccessCount = 1,
|
||||
expirationDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
deletionDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
fileLength = 1,
|
||||
file = createMockFile(number),
|
||||
text = createMockText(number),
|
||||
password = "mockPassword-$number",
|
||||
|
|
|
@ -2,14 +2,17 @@ package com.x8bit.bitwarden.data.vault.datasource.network.model
|
|||
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
fun createMockSend(number: Int): SyncResponseJson.Send =
|
||||
fun createMockSend(
|
||||
number: Int,
|
||||
type: SendTypeJson = SendTypeJson.FILE,
|
||||
): SyncResponseJson.Send =
|
||||
SyncResponseJson.Send(
|
||||
accessCount = 1,
|
||||
notes = "mockNotes-$number",
|
||||
revisionDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
maxAccessCount = 1,
|
||||
shouldHideEmail = false,
|
||||
type = SendTypeJson.FILE,
|
||||
type = type,
|
||||
accessId = "mockAccessId-$number",
|
||||
password = "mockPassword-$number",
|
||||
file = createMockFile(number = number),
|
||||
|
|
|
@ -1,29 +1,73 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.service
|
||||
|
||||
import android.net.Uri
|
||||
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.SendsApi
|
||||
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.UpdateSendResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSend
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSendJsonRequest
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import retrofit2.create
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
|
||||
class SendsServiceTest : BaseServiceTest() {
|
||||
private val clock: Clock = Clock.fixed(
|
||||
Instant.parse("2023-10-27T12:00:00Z"),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
private val azureApi: AzureApi = retrofit.create()
|
||||
private val sendsApi: SendsApi = retrofit.create()
|
||||
|
||||
private val sendsService: SendsService = SendsServiceImpl(
|
||||
azureApi = azureApi,
|
||||
sendsApi = sendsApi,
|
||||
json = json,
|
||||
clock = clock,
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkStatic(Uri::class)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(Uri::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createFileSend should return the correct response`() = runTest {
|
||||
val response = SendFileResponseJson(
|
||||
url = "www.test.com",
|
||||
fileUploadType = SendFileResponseJson.FileUploadType.AZURE,
|
||||
sendResponse = createMockSend(number = 1, type = SendTypeJson.FILE),
|
||||
)
|
||||
server.enqueue(MockResponse().setBody(CREATE_FILE_SEND_SUCCESS_JSON))
|
||||
val result = sendsService.createFileSend(
|
||||
body = createMockSendJsonRequest(number = 1, type = SendTypeJson.FILE),
|
||||
)
|
||||
assertEquals(response, result.getOrThrow())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createSend should return the correct response`() = runTest {
|
||||
server.enqueue(MockResponse().setBody(CREATE_UPDATE_SEND_SUCCESS_JSON))
|
||||
val result = sendsService.createSend(
|
||||
body = createMockSendJsonRequest(number = 1),
|
||||
body = createMockSendJsonRequest(number = 1, type = SendTypeJson.TEXT),
|
||||
)
|
||||
assertEquals(
|
||||
createMockSend(number = 1),
|
||||
|
@ -64,6 +108,46 @@ class SendsServiceTest : BaseServiceTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `uploadFile with Azure uploadFile success should return send`() = runTest {
|
||||
val url = "www.test.com"
|
||||
setupMockUri(url = url, queryParams = mapOf("sv" to "2024-04-03"))
|
||||
val mockSend = createMockSend(number = 1, type = SendTypeJson.FILE)
|
||||
val sendFileResponse = SendFileResponseJson(
|
||||
url = url,
|
||||
fileUploadType = SendFileResponseJson.FileUploadType.AZURE,
|
||||
sendResponse = mockSend,
|
||||
)
|
||||
val encryptedFile = byteArrayOf()
|
||||
server.enqueue(MockResponse().setResponseCode(201))
|
||||
|
||||
val result = sendsService.uploadFile(
|
||||
sendFileResponse = sendFileResponse,
|
||||
encryptedFile = encryptedFile,
|
||||
)
|
||||
|
||||
assertEquals(mockSend, result.getOrThrow())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `uploadFile with Direct uploadFile success should return send`() = runTest {
|
||||
val mockSend = createMockSend(number = 1, type = SendTypeJson.FILE)
|
||||
val sendFileResponse = SendFileResponseJson(
|
||||
url = "www.test.com",
|
||||
fileUploadType = SendFileResponseJson.FileUploadType.DIRECT,
|
||||
sendResponse = mockSend,
|
||||
)
|
||||
val encryptedFile = byteArrayOf()
|
||||
server.enqueue(MockResponse().setResponseCode(201))
|
||||
|
||||
val result = sendsService.uploadFile(
|
||||
sendFileResponse = sendFileResponse,
|
||||
encryptedFile = encryptedFile,
|
||||
)
|
||||
|
||||
assertEquals(mockSend, result.getOrThrow())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `deleteSend should return a Success with the correct data`() = runTest {
|
||||
server.enqueue(MockResponse().setResponseCode(200))
|
||||
|
@ -96,6 +180,19 @@ class SendsServiceTest : BaseServiceTest() {
|
|||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupMockUri(
|
||||
url: String,
|
||||
queryParams: Map<String, String>,
|
||||
): Uri {
|
||||
val mockUri = mockk<Uri> {
|
||||
queryParams.forEach {
|
||||
every { getQueryParameter(it.key) } returns it.value
|
||||
}
|
||||
}
|
||||
every { Uri.parse(url) } returns mockUri
|
||||
return mockUri
|
||||
}
|
||||
}
|
||||
|
||||
private const val CREATE_UPDATE_SEND_SUCCESS_JSON = """
|
||||
|
@ -127,6 +224,39 @@ private const val CREATE_UPDATE_SEND_SUCCESS_JSON = """
|
|||
}
|
||||
"""
|
||||
|
||||
private const val CREATE_FILE_SEND_SUCCESS_JSON = """
|
||||
{
|
||||
"url": "www.test.com",
|
||||
"fileUploadType": "1",
|
||||
"sendResponse": {
|
||||
"id": "mockId-1",
|
||||
"accessId": "mockAccessId-1",
|
||||
"type": 1,
|
||||
"name": "mockName-1",
|
||||
"notes": "mockNotes-1",
|
||||
"file": {
|
||||
"id": "mockId-1",
|
||||
"fileName": "mockFileName-1",
|
||||
"size": 1,
|
||||
"sizeName": "mockSizeName-1"
|
||||
},
|
||||
"text": {
|
||||
"text": "mockText-1",
|
||||
"hidden": false
|
||||
},
|
||||
"key": "mockKey-1",
|
||||
"maxAccessCount": 1,
|
||||
"accessCount": 1,
|
||||
"password": "mockPassword-1",
|
||||
"disabled": false,
|
||||
"revisionDate": "2023-10-27T12:00:00.00Z",
|
||||
"expirationDate": "2023-10-27T12:00:00.00Z",
|
||||
"deletionDate": "2023-10-27T12:00:00.00Z",
|
||||
"hideEmail": false
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
private const val UPDATE_SEND_INVALID_JSON = """
|
||||
{
|
||||
"message": "You do not have permission to edit this.",
|
||||
|
|
|
@ -9,7 +9,10 @@ import java.time.ZonedDateTime
|
|||
/**
|
||||
* Create a mock [Send] with a given [number].
|
||||
*/
|
||||
fun createMockSdkSend(number: Int): Send =
|
||||
fun createMockSdkSend(
|
||||
number: Int,
|
||||
type: SendType = SendType.FILE,
|
||||
): Send =
|
||||
Send(
|
||||
id = "mockId-$number",
|
||||
accessId = "mockAccessId-$number",
|
||||
|
@ -17,7 +20,7 @@ fun createMockSdkSend(number: Int): Send =
|
|||
notes = "mockNotes-$number",
|
||||
key = "mockKey-$number",
|
||||
password = "mockPassword-$number",
|
||||
type = SendType.FILE,
|
||||
type = type,
|
||||
file = createMockSdkFile(number = number),
|
||||
text = createMockSdkText(number = number),
|
||||
maxAccessCount = 1u,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository
|
||||
|
||||
import android.net.Uri
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.CollectionView
|
||||
|
@ -7,6 +8,7 @@ import com.bitwarden.core.FolderView
|
|||
import com.bitwarden.core.InitOrgCryptoRequest
|
||||
import com.bitwarden.core.InitUserCryptoMethod
|
||||
import com.bitwarden.core.InitUserCryptoRequest
|
||||
import com.bitwarden.core.SendType
|
||||
import com.bitwarden.core.SendView
|
||||
import com.bitwarden.crypto.Kdf
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||
|
@ -21,6 +23,8 @@ import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFl
|
|||
import com.x8bit.bitwarden.data.platform.util.asFailure
|
||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||
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.SyncResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson
|
||||
|
@ -46,6 +50,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkCollecti
|
|||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFolder
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkSend
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView
|
||||
import com.x8bit.bitwarden.data.vault.manager.FileManager
|
||||
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
||||
|
@ -68,12 +73,16 @@ import io.mockk.coVerify
|
|||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.net.UnknownHostException
|
||||
|
||||
|
@ -81,6 +90,7 @@ import java.net.UnknownHostException
|
|||
class VaultRepositoryTest {
|
||||
|
||||
private val dispatcherManager: DispatcherManager = FakeDispatcherManager()
|
||||
private val fileManager: FileManager = mockk()
|
||||
private val fakeAuthDiskSource = FakeAuthDiskSource()
|
||||
private val syncService: SyncService = mockk()
|
||||
private val sendsService: SendsService = mockk()
|
||||
|
@ -112,8 +122,19 @@ class VaultRepositoryTest {
|
|||
authDiskSource = fakeAuthDiskSource,
|
||||
vaultLockManager = vaultLockManager,
|
||||
dispatcherManager = dispatcherManager,
|
||||
fileManager = fileManager,
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkStatic(Uri::class)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(Uri::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ciphersStateFlow should emit decrypted list of ciphers when decryptCipherList succeeds`() =
|
||||
runTest {
|
||||
|
@ -1561,51 +1582,181 @@ class VaultRepositoryTest {
|
|||
vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView)
|
||||
} returns IllegalStateException().asFailure()
|
||||
|
||||
val result = vaultRepository.createSend(sendView = mockSendView)
|
||||
val result = vaultRepository.createSend(sendView = mockSendView, fileUri = null)
|
||||
|
||||
assertEquals(CreateSendResult.Error, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `createSend with sendsService createSend failure should return CreateSendResult failure`() =
|
||||
fun `createSend with TEXT and sendsService createSend failure should return CreateSendResult failure`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val mockSendView = createMockSendView(number = 1)
|
||||
val mockSendView = createMockSendView(number = 1, type = SendType.TEXT)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView)
|
||||
} returns createMockSdkSend(number = 1).asSuccess()
|
||||
} returns createMockSdkSend(number = 1, type = SendType.TEXT).asSuccess()
|
||||
coEvery {
|
||||
sendsService.createSend(body = createMockSendJsonRequest(number = 1))
|
||||
sendsService.createSend(
|
||||
body = createMockSendJsonRequest(number = 1, type = SendTypeJson.TEXT)
|
||||
.copy(fileLength = null),
|
||||
)
|
||||
} returns IllegalStateException().asFailure()
|
||||
|
||||
val result = vaultRepository.createSend(sendView = mockSendView)
|
||||
val result = vaultRepository.createSend(sendView = mockSendView, fileUri = null)
|
||||
|
||||
assertEquals(CreateSendResult.Error, result)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `createSend with sendsService createSend success should return CreateSendResult success`() =
|
||||
fun `createSend with TEXT and sendsService createSend success should return CreateSendResult success`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val mockSendView = createMockSendView(number = 1)
|
||||
val mockSdkSend = createMockSdkSend(number = 1)
|
||||
val mockSend = createMockSend(number = 1)
|
||||
val mockSendView = createMockSendView(number = 1, type = SendType.TEXT)
|
||||
val mockSdkSend = createMockSdkSend(number = 1, type = SendType.TEXT)
|
||||
val mockSend = createMockSend(number = 1, type = SendTypeJson.TEXT)
|
||||
val mockSendViewResult = createMockSendView(number = 2)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView)
|
||||
} returns mockSdkSend.asSuccess()
|
||||
coEvery {
|
||||
sendsService.createSend(body = createMockSendJsonRequest(number = 1))
|
||||
sendsService.createSend(
|
||||
body = createMockSendJsonRequest(number = 1, type = SendTypeJson.TEXT)
|
||||
.copy(fileLength = null),
|
||||
)
|
||||
} returns mockSend.asSuccess()
|
||||
coEvery { vaultDiskSource.saveSend(userId, mockSend) } just runs
|
||||
coEvery {
|
||||
vaultSdkSource.decryptSend(userId, mockSdkSend)
|
||||
} returns mockSendViewResult.asSuccess()
|
||||
|
||||
val result = vaultRepository.createSend(sendView = mockSendView)
|
||||
val result = vaultRepository.createSend(sendView = mockSendView, fileUri = null)
|
||||
|
||||
assertEquals(CreateSendResult.Success(mockSendViewResult), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `createSend with FILE and sendsService createFileSend failure should return CreateSendResult failure`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val uri = setupMockUri(url = "www.test.com")
|
||||
val mockSendView = createMockSendView(number = 1)
|
||||
val mockSdkSend = createMockSdkSend(number = 1)
|
||||
val byteArray = byteArrayOf(1)
|
||||
val encryptedByteArray = byteArrayOf(2)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView)
|
||||
} returns mockSdkSend.asSuccess()
|
||||
every { fileManager.uriToByteArray(any()) } returns byteArray
|
||||
coEvery {
|
||||
vaultSdkSource.encryptBuffer(
|
||||
userId = userId,
|
||||
send = mockSdkSend,
|
||||
fileBuffer = byteArray,
|
||||
)
|
||||
} returns encryptedByteArray.asSuccess()
|
||||
coEvery {
|
||||
sendsService.createFileSend(body = createMockSendJsonRequest(number = 1))
|
||||
} returns IllegalStateException().asFailure()
|
||||
|
||||
val result = vaultRepository.createSend(sendView = mockSendView, fileUri = uri)
|
||||
|
||||
assertEquals(CreateSendResult.Error, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `createSend with FILE and sendsService uploadFile failure should return CreateSendResult failure`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val url = "www.test.com"
|
||||
val uri = setupMockUri(url = url)
|
||||
val mockSendView = createMockSendView(number = 1)
|
||||
val mockSdkSend = createMockSdkSend(number = 1)
|
||||
val byteArray = byteArrayOf(1)
|
||||
val encryptedByteArray = byteArrayOf(2)
|
||||
val sendFileResponse = SendFileResponseJson(
|
||||
url = url,
|
||||
fileUploadType = SendFileResponseJson.FileUploadType.AZURE,
|
||||
sendResponse = createMockSend(number = 1, type = SendTypeJson.FILE),
|
||||
)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView)
|
||||
} returns mockSdkSend.asSuccess()
|
||||
every { fileManager.uriToByteArray(any()) } returns byteArray
|
||||
coEvery {
|
||||
vaultSdkSource.encryptBuffer(
|
||||
userId = userId,
|
||||
send = mockSdkSend,
|
||||
fileBuffer = byteArray,
|
||||
)
|
||||
} returns encryptedByteArray.asSuccess()
|
||||
coEvery {
|
||||
sendsService.createFileSend(body = createMockSendJsonRequest(number = 1))
|
||||
} returns sendFileResponse.asSuccess()
|
||||
coEvery {
|
||||
sendsService.uploadFile(
|
||||
sendFileResponse = sendFileResponse,
|
||||
encryptedFile = encryptedByteArray,
|
||||
)
|
||||
} returns Throwable("Fail").asFailure()
|
||||
|
||||
val result = vaultRepository.createSend(sendView = mockSendView, fileUri = uri)
|
||||
|
||||
assertEquals(CreateSendResult.Error, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `createSend with FILE and sendsService uploadFile success should return CreateSendResult success`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val url = "www.test.com"
|
||||
val uri = setupMockUri(url = url)
|
||||
val mockSendView = createMockSendView(number = 1)
|
||||
val mockSdkSend = createMockSdkSend(number = 1)
|
||||
val byteArray = byteArrayOf(1)
|
||||
val encryptedByteArray = byteArrayOf(2)
|
||||
val sendResponse = createMockSend(number = 1)
|
||||
val sendFileResponse = SendFileResponseJson(
|
||||
url = url,
|
||||
fileUploadType = SendFileResponseJson.FileUploadType.AZURE,
|
||||
sendResponse = sendResponse,
|
||||
)
|
||||
val mockSendViewResult = createMockSendView(number = 1)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView)
|
||||
} returns mockSdkSend.asSuccess()
|
||||
every { fileManager.uriToByteArray(any()) } returns byteArray
|
||||
coEvery {
|
||||
vaultSdkSource.encryptBuffer(
|
||||
userId = userId,
|
||||
send = mockSdkSend,
|
||||
fileBuffer = byteArray,
|
||||
)
|
||||
} returns encryptedByteArray.asSuccess()
|
||||
coEvery {
|
||||
sendsService.createFileSend(body = createMockSendJsonRequest(number = 1))
|
||||
} returns sendFileResponse.asSuccess()
|
||||
coEvery {
|
||||
sendsService.uploadFile(
|
||||
sendFileResponse = sendFileResponse,
|
||||
encryptedFile = encryptedByteArray,
|
||||
)
|
||||
} returns sendResponse.asSuccess()
|
||||
coEvery { vaultDiskSource.saveSend(userId, sendResponse) } just runs
|
||||
coEvery {
|
||||
vaultSdkSource.decryptSend(userId, mockSdkSend)
|
||||
} returns mockSendViewResult.asSuccess()
|
||||
|
||||
val result = vaultRepository.createSend(sendView = mockSendView, fileUri = uri)
|
||||
|
||||
assertEquals(CreateSendResult.Success(mockSendViewResult), result)
|
||||
}
|
||||
|
@ -1636,14 +1787,15 @@ class VaultRepositoryTest {
|
|||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val sendId = "sendId1234"
|
||||
val mockSendView = createMockSendView(number = 1)
|
||||
val mockSendView = createMockSendView(number = 1, type = SendType.TEXT)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView)
|
||||
} returns createMockSdkSend(number = 1).asSuccess()
|
||||
} returns createMockSdkSend(number = 1, type = SendType.TEXT).asSuccess()
|
||||
coEvery {
|
||||
sendsService.updateSend(
|
||||
sendId = sendId,
|
||||
body = createMockSendJsonRequest(number = 1),
|
||||
body = createMockSendJsonRequest(number = 1, type = SendTypeJson.TEXT)
|
||||
.copy(fileLength = null),
|
||||
)
|
||||
} returns IllegalStateException().asFailure()
|
||||
|
||||
|
@ -1662,14 +1814,15 @@ class VaultRepositoryTest {
|
|||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val sendId = "sendId1234"
|
||||
val mockSendView = createMockSendView(number = 1)
|
||||
val mockSendView = createMockSendView(number = 1, type = SendType.TEXT)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView)
|
||||
} returns createMockSdkSend(number = 1).asSuccess()
|
||||
} returns createMockSdkSend(number = 1, type = SendType.TEXT).asSuccess()
|
||||
coEvery {
|
||||
sendsService.updateSend(
|
||||
sendId = sendId,
|
||||
body = createMockSendJsonRequest(number = 1),
|
||||
body = createMockSendJsonRequest(number = 1, type = SendTypeJson.TEXT)
|
||||
.copy(fileLength = null),
|
||||
)
|
||||
} returns UpdateSendResponseJson
|
||||
.Invalid(
|
||||
|
@ -1698,19 +1851,22 @@ class VaultRepositoryTest {
|
|||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val sendId = "sendId1234"
|
||||
val mockSendView = createMockSendView(number = 1)
|
||||
val mockSendView = createMockSendView(number = 1, type = SendType.TEXT)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView)
|
||||
} returns createMockSdkSend(number = 1).asSuccess()
|
||||
val mockSend = createMockSend(number = 1)
|
||||
} returns createMockSdkSend(number = 1, type = SendType.TEXT).asSuccess()
|
||||
val mockSend = createMockSend(number = 1, type = SendTypeJson.TEXT)
|
||||
coEvery {
|
||||
sendsService.updateSend(
|
||||
sendId = sendId,
|
||||
body = createMockSendJsonRequest(number = 1),
|
||||
body = createMockSendJsonRequest(number = 1, type = SendTypeJson.TEXT)
|
||||
.copy(fileLength = null),
|
||||
)
|
||||
} returns UpdateSendResponseJson.Success(send = mockSend).asSuccess()
|
||||
coEvery {
|
||||
vaultSdkSource.decryptSend(userId = userId, send = createMockSdkSend(number = 1))
|
||||
vaultSdkSource.decryptSend(
|
||||
userId = userId, send = createMockSdkSend(number = 1, type = SendType.TEXT),
|
||||
)
|
||||
} returns Throwable("Fail").asFailure()
|
||||
coEvery { vaultDiskSource.saveSend(userId = userId, send = mockSend) } just runs
|
||||
|
||||
|
@ -1729,20 +1885,24 @@ class VaultRepositoryTest {
|
|||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val sendId = "sendId1234"
|
||||
val mockSendView = createMockSendView(number = 1)
|
||||
val mockSendView = createMockSendView(number = 1, type = SendType.TEXT)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView)
|
||||
} returns createMockSdkSend(number = 1).asSuccess()
|
||||
val mockSend = createMockSend(number = 1)
|
||||
} returns createMockSdkSend(number = 1, type = SendType.TEXT).asSuccess()
|
||||
val mockSend = createMockSend(number = 1, type = SendTypeJson.TEXT)
|
||||
coEvery {
|
||||
sendsService.updateSend(
|
||||
sendId = sendId,
|
||||
body = createMockSendJsonRequest(number = 1),
|
||||
body = createMockSendJsonRequest(number = 1, type = SendTypeJson.TEXT)
|
||||
.copy(fileLength = null),
|
||||
)
|
||||
} returns UpdateSendResponseJson.Success(send = mockSend).asSuccess()
|
||||
val mockSendViewResult = createMockSendView(number = 2)
|
||||
val mockSendViewResult = createMockSendView(number = 2, type = SendType.TEXT)
|
||||
coEvery {
|
||||
vaultSdkSource.decryptSend(userId = userId, send = createMockSdkSend(number = 1))
|
||||
vaultSdkSource.decryptSend(
|
||||
userId = userId,
|
||||
send = createMockSdkSend(number = 1, type = SendType.TEXT),
|
||||
)
|
||||
} returns mockSendViewResult.asSuccess()
|
||||
coEvery { vaultDiskSource.saveSend(userId = userId, send = mockSend) } just runs
|
||||
|
||||
|
@ -1992,6 +2152,19 @@ class VaultRepositoryTest {
|
|||
}
|
||||
}
|
||||
|
||||
private fun setupMockUri(
|
||||
url: String,
|
||||
queryParams: Map<String, String> = emptyMap(),
|
||||
): Uri {
|
||||
val mockUri = mockk<Uri> {
|
||||
queryParams.forEach {
|
||||
every { getQueryParameter(it.key) } returns it.value
|
||||
}
|
||||
}
|
||||
every { Uri.parse(url) } returns mockUri
|
||||
return mockUri
|
||||
}
|
||||
|
||||
//endregion Helper functions
|
||||
}
|
||||
|
||||
|
|
|
@ -8,13 +8,22 @@ import org.junit.jupiter.api.Test
|
|||
|
||||
class VaultSdkSendExtensionsTest {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toEncryptedNetworkSend should convert a SDK-based Send to network-based Send`() {
|
||||
fun `toEncryptedNetworkSend should convert a SDK-based Send to network-based Send with file length`() {
|
||||
val sdkSend = createMockSdkSend(number = 1)
|
||||
val networkSend = sdkSend.toEncryptedNetworkSend()
|
||||
val networkSend = sdkSend.toEncryptedNetworkSend(fileLength = 1)
|
||||
assertEquals(createMockSendJsonRequest(number = 1), networkSend)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toEncryptedNetworkSend should convert a SDK-based Send to network-based Send without file length`() {
|
||||
val sdkSend = createMockSdkSend(number = 1)
|
||||
val networkSend = sdkSend.toEncryptedNetworkSend(fileLength = null)
|
||||
assertEquals(createMockSendJsonRequest(number = 1).copy(fileLength = null), networkSend)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toEncryptedSdkSend should convert a network-based Send to SDK-based Send`() {
|
||||
val syncSend = createMockSend(number = 1)
|
||||
|
|
|
@ -120,7 +120,7 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
every { toSendUrl(DEFAULT_ENVIRONMENT_URL) } returns sendUrl
|
||||
}
|
||||
coEvery {
|
||||
vaultRepository.createSend(mockSendView)
|
||||
vaultRepository.createSend(sendView = mockSendView, fileUri = null)
|
||||
} returns CreateSendResult.Success(sendView = resultSendView)
|
||||
val viewModel = createViewModel(initialState)
|
||||
|
||||
|
@ -131,7 +131,7 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
assertEquals(initialState, viewModel.stateFlow.value)
|
||||
coVerify(exactly = 1) {
|
||||
vaultRepository.createSend(mockSendView)
|
||||
vaultRepository.createSend(sendView = mockSendView, fileUri = null)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,7 +143,9 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
val initialState = DEFAULT_STATE.copy(viewState = viewState)
|
||||
val mockSendView = mockk<SendView>()
|
||||
every { viewState.toSendView(clock) } returns mockSendView
|
||||
coEvery { vaultRepository.createSend(mockSendView) } returns CreateSendResult.Error
|
||||
coEvery {
|
||||
vaultRepository.createSend(sendView = mockSendView, fileUri = null)
|
||||
} returns CreateSendResult.Error
|
||||
val viewModel = createViewModel(initialState)
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
|
@ -168,7 +170,7 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
coVerify(exactly = 1) {
|
||||
vaultRepository.createSend(mockSendView)
|
||||
vaultRepository.createSend(sendView = mockSendView, fileUri = null)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue