mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
BIT-1547: Setup needed logic to support push notification syncs (#837)
This commit is contained in:
parent
7c4092a539
commit
474025b893
15 changed files with 433 additions and 0 deletions
|
@ -8,6 +8,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
|||
import okhttp3.MultipartBody
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
|
@ -92,4 +93,12 @@ interface CiphersApi {
|
|||
suspend fun restoreCipher(
|
||||
@Path("cipherId") cipherId: String,
|
||||
): Result<Unit>
|
||||
|
||||
/**
|
||||
* Gets a cipher.
|
||||
*/
|
||||
@GET("ciphers/{cipherId}")
|
||||
suspend fun getCipher(
|
||||
@Path("cipherId") cipherId: String,
|
||||
): Result<SyncResponseJson.Cipher>
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest
|
|||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
|
@ -19,6 +20,14 @@ interface FoldersApi {
|
|||
@POST("folders")
|
||||
suspend fun createFolder(@Body body: FolderJsonRequest): Result<SyncResponseJson.Folder>
|
||||
|
||||
/**
|
||||
* Gets a folder.
|
||||
*/
|
||||
@GET("folders/{folderId}")
|
||||
suspend fun getFolder(
|
||||
@Path("folderId") folderId: String,
|
||||
): Result<SyncResponseJson.Folder>
|
||||
|
||||
/**
|
||||
* Updates a folder.
|
||||
*/
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
|||
import okhttp3.MultipartBody
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
|
@ -59,4 +60,10 @@ interface SendsApi {
|
|||
*/
|
||||
@PUT("sends/{sendId}/remove-password")
|
||||
suspend fun removeSendPassword(@Path("sendId") sendId: String): Result<SyncResponseJson.Send>
|
||||
|
||||
/**
|
||||
* Gets a send.
|
||||
*/
|
||||
@GET("sends/{sendId}")
|
||||
suspend fun getSend(@Path("sendId") sendId: String): Result<SyncResponseJson.Send>
|
||||
}
|
||||
|
|
|
@ -70,4 +70,9 @@ interface CiphersService {
|
|||
* Attempt to restore a cipher.
|
||||
*/
|
||||
suspend fun restoreCipher(cipherId: String): Result<Unit>
|
||||
|
||||
/**
|
||||
* Attempt to retrieve a cipher.
|
||||
*/
|
||||
suspend fun getCipher(cipherId: String): Result<SyncResponseJson.Cipher>
|
||||
}
|
||||
|
|
|
@ -129,4 +129,9 @@ class CiphersServiceImpl(
|
|||
|
||||
override suspend fun restoreCipher(cipherId: String): Result<Unit> =
|
||||
ciphersApi.restoreCipher(cipherId = cipherId)
|
||||
|
||||
override suspend fun getCipher(
|
||||
cipherId: String,
|
||||
): Result<SyncResponseJson.Cipher> =
|
||||
ciphersApi.getCipher(cipherId = cipherId)
|
||||
}
|
||||
|
|
|
@ -25,4 +25,9 @@ interface FolderService {
|
|||
* Attempt to hard delete a folder.
|
||||
*/
|
||||
suspend fun deleteFolder(folderId: String): Result<Unit>
|
||||
|
||||
/**
|
||||
* Attempt to retrieve a folder.
|
||||
*/
|
||||
suspend fun getFolder(folderId: String): Result<SyncResponseJson.Folder>
|
||||
}
|
||||
|
|
|
@ -37,4 +37,9 @@ class FolderServiceImpl constructor(
|
|||
|
||||
override suspend fun deleteFolder(folderId: String): Result<Unit> =
|
||||
foldersApi.deleteFolder(folderId = folderId)
|
||||
|
||||
override suspend fun getFolder(
|
||||
folderId: String,
|
||||
): Result<SyncResponseJson.Folder> = foldersApi
|
||||
.getFolder(folderId = folderId)
|
||||
}
|
||||
|
|
|
@ -52,4 +52,9 @@ interface SendsService {
|
|||
suspend fun removeSendPassword(
|
||||
sendId: String,
|
||||
): Result<UpdateSendResponseJson>
|
||||
|
||||
/**
|
||||
* Attempt to retrieve a send.
|
||||
*/
|
||||
suspend fun getSend(sendId: String): Result<SyncResponseJson.Send>
|
||||
}
|
||||
|
|
|
@ -112,4 +112,9 @@ class SendsServiceImpl(
|
|||
)
|
||||
?: throw throwable
|
||||
}
|
||||
|
||||
override suspend fun getSend(
|
||||
sendId: String,
|
||||
): Result<SyncResponseJson.Send> =
|
||||
sendsApi.getSend(sendId = sendId)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,11 @@ import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
|
|||
import com.x8bit.bitwarden.data.auth.repository.util.toUpdatedUserStateJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.isNoConnectionError
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherUpsertData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderUpsertData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncSendUpsertData
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.combineDataStates
|
||||
|
@ -97,6 +101,7 @@ import kotlinx.coroutines.flow.onStart
|
|||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import retrofit2.HttpException
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
@ -123,6 +128,7 @@ class VaultRepositoryImpl(
|
|||
private val fileManager: FileManager,
|
||||
private val vaultLockManager: VaultLockManager,
|
||||
private val totpCodeManager: TotpCodeManager,
|
||||
private val pushManager: PushManager,
|
||||
private val clock: Clock,
|
||||
dispatcherManager: DispatcherManager,
|
||||
) : VaultRepository,
|
||||
|
@ -229,6 +235,21 @@ class VaultRepositoryImpl(
|
|||
observeVaultDiskSends(activeUserId)
|
||||
}
|
||||
.launchIn(unconfinedScope)
|
||||
|
||||
pushManager
|
||||
.syncCipherUpsertFlow
|
||||
.onEach(::syncCipherIfNecessary)
|
||||
.launchIn(ioScope)
|
||||
|
||||
pushManager
|
||||
.syncSendUpsertFlow
|
||||
.onEach(::syncSendIfNecessary)
|
||||
.launchIn(ioScope)
|
||||
|
||||
pushManager
|
||||
.syncFolderUpsertFlow
|
||||
.onEach(::syncFolderIfNecessary)
|
||||
.launchIn(ioScope)
|
||||
}
|
||||
|
||||
override fun clearUnlockedData() {
|
||||
|
@ -1209,6 +1230,80 @@ class VaultRepositoryImpl(
|
|||
)
|
||||
}
|
||||
.onEach { mutableSendDataStateFlow.value = it }
|
||||
|
||||
//region Push notification helpers
|
||||
/**
|
||||
* Syncs an individual cipher contained in [syncCipherUpsertData] to disk if certain criteria
|
||||
* are met. If the resource cannot be found cloud-side, and it was updated, delete it from disk
|
||||
* for now.
|
||||
*/
|
||||
private suspend fun syncCipherIfNecessary(syncCipherUpsertData: SyncCipherUpsertData) {
|
||||
val userId = activeUserId ?: return
|
||||
|
||||
// TODO Handle other filtering logic including revision date comparison. This will still be
|
||||
// handled as part of BIT-1547.
|
||||
|
||||
val cipherId = syncCipherUpsertData.cipherId
|
||||
val isUpdate = syncCipherUpsertData.isUpdate
|
||||
ciphersService
|
||||
.getCipher(cipherId)
|
||||
.fold(
|
||||
onSuccess = { vaultDiskSource.saveCipher(userId, it) },
|
||||
onFailure = {
|
||||
// Delete any updates if it's missing from the server
|
||||
val httpException = it as? HttpException
|
||||
@Suppress("MagicNumber")
|
||||
if (httpException?.code() == 404 && isUpdate) {
|
||||
vaultDiskSource.deleteCipher(userId = userId, cipherId = cipherId)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs an individual send contained in [syncSendUpsertData] to disk if certain criteria are
|
||||
* met. If the resource cannot be found cloud-side, and it was updated, delete it from disk for
|
||||
* now.
|
||||
*/
|
||||
private suspend fun syncSendIfNecessary(syncSendUpsertData: SyncSendUpsertData) {
|
||||
val userId = activeUserId ?: return
|
||||
|
||||
// TODO Handle other filtering logic including revision date comparison. This will still be
|
||||
// handled as part of BIT-1547.
|
||||
|
||||
val sendId = syncSendUpsertData.sendId
|
||||
val isUpdate = syncSendUpsertData.isUpdate
|
||||
sendsService
|
||||
.getSend(sendId)
|
||||
.fold(
|
||||
onSuccess = { vaultDiskSource.saveSend(userId, it) },
|
||||
onFailure = {
|
||||
// Delete any updates if it's missing from the server
|
||||
val httpException = it as? HttpException
|
||||
@Suppress("MagicNumber")
|
||||
if (httpException?.code() == 404 && isUpdate) {
|
||||
vaultDiskSource.deleteSend(userId = userId, sendId = sendId)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs an individual folder contained in [syncFolderUpsertData] to disk if certain criteria
|
||||
* are met.
|
||||
*/
|
||||
private suspend fun syncFolderIfNecessary(syncFolderUpsertData: SyncFolderUpsertData) {
|
||||
val userId = activeUserId ?: return
|
||||
|
||||
// TODO Handle other filtering logic including revision date comparison. This will still be
|
||||
// handled as part of BIT-1547.
|
||||
|
||||
val folderId = syncFolderUpsertData.folderId
|
||||
folderService
|
||||
.getFolder(folderId)
|
||||
.onSuccess { vaultDiskSource.saveFolder(userId, it) }
|
||||
}
|
||||
//endregion Push Notification helpers
|
||||
}
|
||||
|
||||
private fun <T> Throwable.toNetworkOrErrorState(data: T?): DataState<T> =
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.vault.repository.di
|
|||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
|
||||
|
@ -43,6 +44,7 @@ object VaultRepositoryModule {
|
|||
vaultLockManager: VaultLockManager,
|
||||
dispatcherManager: DispatcherManager,
|
||||
totpCodeManager: TotpCodeManager,
|
||||
pushManager: PushManager,
|
||||
clock: Clock,
|
||||
): VaultRepository = VaultRepositoryImpl(
|
||||
syncService = syncService,
|
||||
|
@ -57,6 +59,7 @@ object VaultRepositoryModule {
|
|||
vaultLockManager = vaultLockManager,
|
||||
dispatcherManager = dispatcherManager,
|
||||
totpCodeManager = totpCodeManager,
|
||||
pushManager = pushManager,
|
||||
clock = clock,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -202,6 +202,16 @@ class CiphersServiceTest : BaseServiceTest() {
|
|||
val result = ciphersService.restoreCipher(cipherId = cipherId)
|
||||
assertEquals(Unit, result.getOrThrow())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getCipher should return the correct response`() = runTest {
|
||||
server.enqueue(MockResponse().setBody(CREATE_UPDATE_CIPHER_SUCCESS_JSON))
|
||||
val result = ciphersService.getCipher(cipherId = "mockId-1")
|
||||
assertEquals(
|
||||
createMockCipher(number = 1),
|
||||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupMockUri(
|
||||
|
|
|
@ -71,6 +71,16 @@ class FoldersServiceTest : BaseServiceTest() {
|
|||
val result = folderService.deleteFolder(DEFAULT_ID)
|
||||
assertEquals(Unit, result.getOrThrow())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getFolder should return the correct response`() = runTest {
|
||||
server.enqueue(MockResponse().setBody(CREATE_UPDATE_FOLDER_SUCCESS_JSON))
|
||||
val result = folderService.getFolder("FolderId")
|
||||
assertEquals(
|
||||
DEFAULT_FOLDER,
|
||||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val DEFAULT_ID = "FolderId"
|
||||
|
|
|
@ -182,6 +182,14 @@ class SendsServiceTest : BaseServiceTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSend should return the correct response`() = runTest {
|
||||
val response = createMockSend(number = 1)
|
||||
server.enqueue(MockResponse().setBody(CREATE_UPDATE_SEND_SUCCESS_JSON))
|
||||
val result = sendsService.getSend("mockId-1")
|
||||
assertEquals(response, result.getOrThrow())
|
||||
}
|
||||
|
||||
private fun setupMockUri(
|
||||
url: String,
|
||||
queryParams: Map<String, String>,
|
||||
|
|
|
@ -22,7 +22,11 @@ import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
|
|||
import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncCipherUpsertData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncFolderUpsertData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SyncSendUpsertData
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.data.platform.util.asFailure
|
||||
|
@ -117,6 +121,7 @@ import org.junit.jupiter.api.Assertions.assertEquals
|
|||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import retrofit2.HttpException
|
||||
import java.net.UnknownHostException
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
|
@ -157,6 +162,15 @@ class VaultRepositoryTest {
|
|||
every { lockVaultForCurrentUser() } just runs
|
||||
}
|
||||
|
||||
private val mutableSyncCipherUpsertFlow = bufferedMutableSharedFlow<SyncCipherUpsertData>()
|
||||
private val mutableSyncSendUpsertFlow = bufferedMutableSharedFlow<SyncSendUpsertData>()
|
||||
private val mutableSyncFolderUpsertFlow = bufferedMutableSharedFlow<SyncFolderUpsertData>()
|
||||
private val pushManager: PushManager = mockk {
|
||||
every { syncCipherUpsertFlow } returns mutableSyncCipherUpsertFlow
|
||||
every { syncSendUpsertFlow } returns mutableSyncSendUpsertFlow
|
||||
every { syncFolderUpsertFlow } returns mutableSyncFolderUpsertFlow
|
||||
}
|
||||
|
||||
private val vaultRepository = VaultRepositoryImpl(
|
||||
syncService = syncService,
|
||||
sendsService = sendsService,
|
||||
|
@ -169,6 +183,7 @@ class VaultRepositoryTest {
|
|||
vaultLockManager = vaultLockManager,
|
||||
dispatcherManager = dispatcherManager,
|
||||
totpCodeManager = totpCodeManager,
|
||||
pushManager = pushManager,
|
||||
fileManager = fileManager,
|
||||
clock = clock,
|
||||
)
|
||||
|
@ -3654,6 +3669,243 @@ class VaultRepositoryTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `syncCipherUpsertFlow success should make a request for a cipher and then store it`() =
|
||||
runTest {
|
||||
val cipherId = "mockId-1"
|
||||
val cipher: SyncResponseJson.Cipher = mockk()
|
||||
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
|
||||
coEvery {
|
||||
ciphersService.getCipher(cipherId)
|
||||
} returns cipher.asSuccess()
|
||||
|
||||
coEvery {
|
||||
vaultDiskSource.saveCipher(any(), any())
|
||||
} just runs
|
||||
|
||||
mutableSyncCipherUpsertFlow.tryEmit(
|
||||
SyncCipherUpsertData(
|
||||
cipherId = cipherId,
|
||||
revisionDate = ZonedDateTime.now(),
|
||||
isUpdate = false,
|
||||
),
|
||||
)
|
||||
coVerify(exactly = 1) {
|
||||
ciphersService.getCipher(cipherId)
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
cipher = cipher,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `syncCipherUpsertFlow update failure with 404 code should make a request for a cipher and then delete it`() =
|
||||
runTest {
|
||||
every {
|
||||
pushManager.syncCipherUpsertFlow
|
||||
}
|
||||
|
||||
val cipherId = "mockId-1"
|
||||
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
|
||||
val response: HttpException = mockk {
|
||||
every { code() } returns 404
|
||||
}
|
||||
coEvery {
|
||||
ciphersService.getCipher(cipherId)
|
||||
} returns response.asFailure()
|
||||
|
||||
coEvery {
|
||||
vaultDiskSource.deleteCipher(any(), any())
|
||||
} just runs
|
||||
|
||||
mutableSyncCipherUpsertFlow.tryEmit(
|
||||
SyncCipherUpsertData(
|
||||
cipherId = cipherId,
|
||||
revisionDate = ZonedDateTime.now(),
|
||||
isUpdate = true,
|
||||
),
|
||||
)
|
||||
coVerify(exactly = 1) {
|
||||
ciphersService.getCipher(cipherId)
|
||||
vaultDiskSource.deleteCipher(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
cipherId = cipherId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `syncCipherUpsertFlow create failure with 404 code should make a request for a cipher and do nothing`() =
|
||||
runTest {
|
||||
val cipherId = "mockId-1"
|
||||
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
|
||||
val response: HttpException = mockk {
|
||||
every { code() } returns 404
|
||||
}
|
||||
coEvery {
|
||||
ciphersService.getCipher(cipherId)
|
||||
} returns response.asFailure()
|
||||
|
||||
mutableSyncCipherUpsertFlow.tryEmit(
|
||||
SyncCipherUpsertData(
|
||||
cipherId = cipherId,
|
||||
revisionDate = ZonedDateTime.now(),
|
||||
isUpdate = false,
|
||||
),
|
||||
)
|
||||
coVerify(exactly = 1) {
|
||||
ciphersService.getCipher(cipherId)
|
||||
}
|
||||
coVerify(exactly = 0) {
|
||||
vaultDiskSource.deleteCipher(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
cipherId = cipherId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `syncSendUpsertFlow success should make a request for a send and then store it`() =
|
||||
runTest {
|
||||
val sendId = "mockId-1"
|
||||
val send: SyncResponseJson.Send = mockk()
|
||||
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
|
||||
coEvery {
|
||||
sendsService.getSend(sendId)
|
||||
} returns send.asSuccess()
|
||||
|
||||
coEvery {
|
||||
vaultDiskSource.saveSend(any(), any())
|
||||
} just runs
|
||||
|
||||
mutableSyncSendUpsertFlow.tryEmit(
|
||||
SyncSendUpsertData(
|
||||
sendId = sendId,
|
||||
revisionDate = ZonedDateTime.now(),
|
||||
isUpdate = false,
|
||||
),
|
||||
)
|
||||
coVerify(exactly = 1) {
|
||||
sendsService.getSend(sendId)
|
||||
vaultDiskSource.saveSend(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
send = send,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `syncSendUpsertFlow update failure with 404 code should make a request for a send and then delete it`() =
|
||||
runTest {
|
||||
val sendId = "mockId-1"
|
||||
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
|
||||
val response: HttpException = mockk {
|
||||
every { code() } returns 404
|
||||
}
|
||||
coEvery {
|
||||
sendsService.getSend(sendId)
|
||||
} returns response.asFailure()
|
||||
|
||||
coEvery {
|
||||
vaultDiskSource.deleteSend(any(), any())
|
||||
} just runs
|
||||
|
||||
mutableSyncSendUpsertFlow.tryEmit(
|
||||
SyncSendUpsertData(
|
||||
sendId = sendId,
|
||||
revisionDate = ZonedDateTime.now(),
|
||||
isUpdate = true,
|
||||
),
|
||||
)
|
||||
coVerify(exactly = 1) {
|
||||
sendsService.getSend(sendId)
|
||||
vaultDiskSource.deleteSend(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
sendId = sendId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `syncSendUpsertFlow create failure with 404 code should make a request for a send and do nothing`() =
|
||||
runTest {
|
||||
val sendId = "mockId-1"
|
||||
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
|
||||
val response: HttpException = mockk {
|
||||
every { code() } returns 404
|
||||
}
|
||||
coEvery {
|
||||
sendsService.getSend(sendId)
|
||||
} returns response.asFailure()
|
||||
|
||||
mutableSyncSendUpsertFlow.tryEmit(
|
||||
SyncSendUpsertData(
|
||||
sendId = sendId,
|
||||
revisionDate = ZonedDateTime.now(),
|
||||
isUpdate = false,
|
||||
),
|
||||
)
|
||||
coVerify(exactly = 1) {
|
||||
sendsService.getSend(sendId)
|
||||
}
|
||||
coVerify(exactly = 0) {
|
||||
vaultDiskSource.deleteSend(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
sendId = sendId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `mutableSyncFolderUpsertFlow success should make a request for a folder and then store it`() =
|
||||
runTest {
|
||||
val folderId = "mockId-1"
|
||||
val folder: SyncResponseJson.Folder = mockk()
|
||||
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
|
||||
coEvery {
|
||||
folderService.getFolder(folderId)
|
||||
} returns folder.asSuccess()
|
||||
|
||||
coEvery {
|
||||
vaultDiskSource.saveFolder(any(), any())
|
||||
} just runs
|
||||
|
||||
mutableSyncFolderUpsertFlow.tryEmit(
|
||||
SyncFolderUpsertData(
|
||||
folderId = folderId,
|
||||
revisionDate = ZonedDateTime.now(),
|
||||
isUpdate = false,
|
||||
),
|
||||
)
|
||||
coVerify(exactly = 1) {
|
||||
folderService.getFolder(folderId)
|
||||
vaultDiskSource.saveFolder(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
folder = folder,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//region Helper functions
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue