PM-25478: Update sends and folders while vault is locked (#5837)

This commit is contained in:
David Perez
2025-09-05 09:32:49 -05:00
committed by GitHub
parent 393931a5c6
commit 2bd4834b14
2 changed files with 107 additions and 227 deletions

View File

@@ -114,7 +114,6 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
@@ -1397,15 +1396,14 @@ class VaultRepositoryImpl(
val isUpdate = syncSendUpsertData.isUpdate
val revisionDate = syncSendUpsertData.revisionDate
val localSend = sendDataStateFlow
.mapNotNull { it.data }
val localSend = vaultDiskSource
.getSends(userId = userId)
.first()
.sendViewList
.find { it.id == sendId }
val isValidCreate = !isUpdate && localSend == null
val isValidUpdate = isUpdate &&
localSend != null &&
localSend.revisionDate.epochSecond < revisionDate.toEpochSecond()
localSend.revisionDate.toEpochSecond() < revisionDate.toEpochSecond()
if (!isValidCreate && !isValidUpdate) return
@@ -1447,21 +1445,20 @@ class VaultRepositoryImpl(
val folderId = syncFolderUpsertData.folderId
val isUpdate = syncFolderUpsertData.isUpdate
val revisionDate = syncFolderUpsertData.revisionDate
val localFolder = foldersStateFlow
.mapNotNull { it.data }
val localFolder = vaultDiskSource
.getFolders(userId = userId)
.first()
.find { it.id == folderId }
val isValidCreate = !isUpdate && localFolder == null
val isValidUpdate = isUpdate &&
localFolder != null &&
localFolder.revisionDate.epochSecond < revisionDate.toEpochSecond()
localFolder.revisionDate.toEpochSecond() < revisionDate.toEpochSecond()
if (!isValidCreate && !isValidUpdate) return
folderService
.getFolder(folderId)
.onSuccess { vaultDiskSource.saveFolder(userId, it) }
.getFolder(folderId = folderId)
.onSuccess { vaultDiskSource.saveFolder(userId = userId, folder = it) }
}
//endregion Push Notification helpers

View File

@@ -3788,53 +3788,37 @@ class VaultRepositoryTest {
fun `syncSendUpsertFlow update failure with 404 code should make a request for a send and then delete it`() =
runTest {
val number = 1
val userId = MOCK_USER_STATE.activeUserId
val sendId = "mockId-$number"
fakeAuthDiskSource.userState = MOCK_USER_STATE
val response: HttpException = mockk {
every { code() } returns 404
}
coEvery { sendsService.getSend(sendId = sendId) } returns response.asFailure()
coEvery {
sendsService.getSend(sendId)
} returns response.asFailure()
coEvery {
vaultDiskSource.deleteSend(any(), any())
vaultDiskSource.deleteSend(userId = userId, sendId = sendId)
} just runs
fakeAuthDiskSource.userState = MOCK_USER_STATE
setVaultToUnlocked(userId = MOCK_USER_STATE.activeUserId)
val sendView = createMockSendView(number = number)
val sendView = createMockSend(
number = number,
revisionDate = ZonedDateTime.now(clock).minus(5, ChronoUnit.MINUTES),
)
coEvery {
vaultSdkSource.decryptSendList(
userId = MOCK_USER_STATE.activeUserId,
sendList = listOf(createMockSdkSend(number = number)),
)
} returns listOf(sendView).asSuccess()
vaultDiskSource.getSends(userId = userId)
} returns MutableStateFlow(listOf(sendView))
val sendsFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Send>>()
setupVaultDiskSourceFlows(sendsFlow = sendsFlow)
vaultRepository.sendDataStateFlow.test {
// Populate and consume items related to the sends flow
awaitItem()
sendsFlow.tryEmit(listOf(createMockSend(number = number)))
awaitItem()
mutableSyncSendUpsertFlow.tryEmit(
SyncSendUpsertData(
sendId = sendId,
revisionDate = ZonedDateTime.now(),
isUpdate = true,
),
)
}
mutableSyncSendUpsertFlow.tryEmit(
SyncSendUpsertData(
sendId = sendId,
revisionDate = ZonedDateTime.now(clock),
isUpdate = true,
),
)
coVerify(exactly = 1) {
sendsService.getSend(sendId)
vaultDiskSource.deleteSend(
userId = MOCK_USER_STATE.activeUserId,
sendId = sendId,
)
sendsService.getSend(sendId = sendId)
vaultDiskSource.deleteSend(userId = userId, sendId = sendId)
}
}
@@ -3842,51 +3826,31 @@ class VaultRepositoryTest {
@Test
fun `syncSendUpsertFlow create failure with 404 code should make a request for a send and do nothing`() =
runTest {
val userId = MOCK_USER_STATE.activeUserId
val sendId = "mockId-1"
fakeAuthDiskSource.userState = MOCK_USER_STATE
setVaultToUnlocked(userId = MOCK_USER_STATE.activeUserId)
val response: HttpException = mockk {
every { code() } returns 404
}
coEvery { sendsService.getSend(sendId = sendId) } returns response.asFailure()
coEvery {
sendsService.getSend(sendId)
} returns response.asFailure()
vaultDiskSource.getSends(userId = userId)
} returns MutableStateFlow(emptyList())
coEvery {
vaultSdkSource.decryptSendList(
userId = MOCK_USER_STATE.activeUserId,
sendList = listOf(),
)
} returns emptyList<SendView>().asSuccess()
val sendsFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Send>>()
setupVaultDiskSourceFlows(sendsFlow = sendsFlow)
vaultRepository.sendDataStateFlow.test {
// Populate and consume items related to the sends flow
awaitItem()
sendsFlow.tryEmit(emptyList())
awaitItem()
mutableSyncSendUpsertFlow.tryEmit(
SyncSendUpsertData(
sendId = sendId,
revisionDate = ZonedDateTime.now(),
isUpdate = false,
),
)
}
mutableSyncSendUpsertFlow.tryEmit(
SyncSendUpsertData(
sendId = sendId,
revisionDate = ZonedDateTime.now(clock),
isUpdate = false,
),
)
coVerify(exactly = 1) {
sendsService.getSend(sendId)
sendsService.getSend(sendId = sendId)
}
coVerify(exactly = 0) {
vaultDiskSource.deleteSend(
userId = MOCK_USER_STATE.activeUserId,
sendId = sendId,
)
vaultDiskSource.deleteSend(userId = userId, sendId = sendId)
}
}
@@ -3895,50 +3859,28 @@ class VaultRepositoryTest {
fun `syncSendUpsertFlow valid create success should make a request for a send and then store it`() =
runTest {
val number = 1
val userId = MOCK_USER_STATE.activeUserId
val sendId = "mockId-$number"
fakeAuthDiskSource.userState = MOCK_USER_STATE
setVaultToUnlocked(userId = MOCK_USER_STATE.activeUserId)
coEvery {
vaultSdkSource.decryptSendList(
userId = MOCK_USER_STATE.activeUserId,
sendList = listOf(),
)
} returns listOf<SendView>().asSuccess()
vaultDiskSource.getSends(userId = userId)
} returns MutableStateFlow(emptyList())
val send = mockk<SyncResponseJson.Send>()
coEvery { sendsService.getSend(sendId = sendId) } returns send.asSuccess()
coEvery { vaultDiskSource.saveSend(userId = userId, send = send) } just runs
val sendsFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Send>>()
setupVaultDiskSourceFlows(sendsFlow = sendsFlow)
val send: SyncResponseJson.Send = mockk()
coEvery {
sendsService.getSend(sendId)
} returns send.asSuccess()
coEvery {
vaultDiskSource.saveSend(any(), any())
} just runs
vaultRepository.sendDataStateFlow.test {
// Populate and consume items related to the sends flow
awaitItem()
sendsFlow.tryEmit(listOf())
awaitItem()
mutableSyncSendUpsertFlow.tryEmit(
SyncSendUpsertData(
sendId = sendId,
revisionDate = ZonedDateTime.now(),
isUpdate = false,
),
)
}
mutableSyncSendUpsertFlow.tryEmit(
SyncSendUpsertData(
sendId = sendId,
revisionDate = ZonedDateTime.now(clock),
isUpdate = false,
),
)
coVerify(exactly = 1) {
sendsService.getSend(sendId)
vaultDiskSource.saveSend(
userId = MOCK_USER_STATE.activeUserId,
send = send,
)
sendsService.getSend(sendId = sendId)
vaultDiskSource.saveSend(userId = userId, send = send)
}
}
@@ -3947,51 +3889,33 @@ class VaultRepositoryTest {
fun `syncSendUpsertFlow valid update success should make a request for a send and then store it`() =
runTest {
val number = 1
val userId = MOCK_USER_STATE.activeUserId
val sendId = "mockId-$number"
fakeAuthDiskSource.userState = MOCK_USER_STATE
setVaultToUnlocked(userId = MOCK_USER_STATE.activeUserId)
val sendView = createMockSendView(number = number)
val sendView = createMockSend(
number = number,
revisionDate = ZonedDateTime.now(clock).minus(5, ChronoUnit.MINUTES),
)
coEvery {
vaultSdkSource.decryptSendList(
userId = MOCK_USER_STATE.activeUserId,
sendList = listOf(createMockSdkSend(number = number)),
)
} returns listOf(sendView).asSuccess()
vaultDiskSource.getSends(userId = userId)
} returns MutableStateFlow(listOf(sendView))
val sendsFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Send>>()
setupVaultDiskSourceFlows(sendsFlow = sendsFlow)
val send = mockk<SyncResponseJson.Send>()
coEvery { sendsService.getSend(sendId = sendId) } returns send.asSuccess()
coEvery { vaultDiskSource.saveSend(userId = userId, send = send) } just runs
val send: SyncResponseJson.Send = mockk()
coEvery {
sendsService.getSend(sendId)
} returns send.asSuccess()
coEvery {
vaultDiskSource.saveSend(any(), any())
} just runs
vaultRepository.sendDataStateFlow.test {
// Populate and consume items related to the sends flow
awaitItem()
sendsFlow.tryEmit(listOf(createMockSend(number = number)))
awaitItem()
mutableSyncSendUpsertFlow.tryEmit(
SyncSendUpsertData(
sendId = sendId,
revisionDate = ZonedDateTime.now(),
isUpdate = true,
),
)
}
mutableSyncSendUpsertFlow.tryEmit(
SyncSendUpsertData(
sendId = sendId,
revisionDate = ZonedDateTime.now(clock),
isUpdate = true,
),
)
coVerify(exactly = 1) {
sendsService.getSend(sendId)
vaultDiskSource.saveSend(
userId = MOCK_USER_STATE.activeUserId,
send = send,
)
sendsService.getSend(sendId = sendId)
vaultDiskSource.saveSend(userId = userId, send = send)
}
}
@@ -4140,50 +4064,28 @@ class VaultRepositoryTest {
fun `syncFolderUpsertFlow valid create success should make a request for a folder and then store it`() =
runTest {
val number = 1
val userId = MOCK_USER_STATE.activeUserId
val folderId = "mockId-$number"
fakeAuthDiskSource.userState = MOCK_USER_STATE
setVaultToUnlocked(userId = MOCK_USER_STATE.activeUserId)
coEvery {
vaultSdkSource.decryptFolderList(
userId = MOCK_USER_STATE.activeUserId,
folderList = listOf(),
)
} returns listOf<FolderView>().asSuccess()
vaultDiskSource.getFolders(userId = userId)
} returns MutableStateFlow(emptyList())
val folder = mockk<SyncResponseJson.Folder>()
coEvery { folderService.getFolder(folderId = folderId) } returns folder.asSuccess()
coEvery { vaultDiskSource.saveFolder(userId = userId, folder = folder) } just runs
val foldersFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Folder>>()
setupVaultDiskSourceFlows(foldersFlow = foldersFlow)
val folder: SyncResponseJson.Folder = mockk()
coEvery {
folderService.getFolder(folderId)
} returns folder.asSuccess()
coEvery {
vaultDiskSource.saveFolder(any(), any())
} just runs
vaultRepository.foldersStateFlow.test {
// Populate and consume items related to the folders flow
awaitItem()
foldersFlow.tryEmit(listOf())
awaitItem()
mutableSyncFolderUpsertFlow.tryEmit(
SyncFolderUpsertData(
folderId = folderId,
revisionDate = ZonedDateTime.now(),
isUpdate = false,
),
)
}
mutableSyncFolderUpsertFlow.tryEmit(
SyncFolderUpsertData(
folderId = folderId,
revisionDate = ZonedDateTime.now(clock),
isUpdate = false,
),
)
coVerify(exactly = 1) {
folderService.getFolder(folderId)
vaultDiskSource.saveFolder(
userId = MOCK_USER_STATE.activeUserId,
folder = folder,
)
folderService.getFolder(folderId = folderId)
vaultDiskSource.saveFolder(userId = userId, folder = folder)
}
}
@@ -4192,51 +4094,32 @@ class VaultRepositoryTest {
fun `syncFolderUpsertFlow valid update success should make a request for a folder and then store it`() =
runTest {
val number = 1
val userId = MOCK_USER_STATE.activeUserId
val folderId = "mockId-$number"
fakeAuthDiskSource.userState = MOCK_USER_STATE
setVaultToUnlocked(userId = MOCK_USER_STATE.activeUserId)
val folderView = createMockFolderView(number = number)
val folderView = createMockFolder(
number = number,
revisionDate = ZonedDateTime.now(clock).minus(5, ChronoUnit.MINUTES),
)
coEvery {
vaultSdkSource.decryptFolderList(
userId = MOCK_USER_STATE.activeUserId,
folderList = listOf(createMockSdkFolder(number = number)),
)
} returns listOf(folderView).asSuccess()
vaultDiskSource.getFolders(userId = userId)
} returns MutableStateFlow(listOf(folderView))
val folder = mockk<SyncResponseJson.Folder>()
coEvery { folderService.getFolder(folderId = folderId) } returns folder.asSuccess()
coEvery { vaultDiskSource.saveFolder(userId = userId, folder = folder) } just runs
val foldersFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Folder>>()
setupVaultDiskSourceFlows(foldersFlow = foldersFlow)
val folder: SyncResponseJson.Folder = mockk()
coEvery {
folderService.getFolder(folderId)
} returns folder.asSuccess()
coEvery {
vaultDiskSource.saveFolder(any(), any())
} just runs
vaultRepository.foldersStateFlow.test {
// Populate and consume items related to the folders flow
awaitItem()
foldersFlow.tryEmit(listOf(createMockFolder(number = number)))
awaitItem()
mutableSyncFolderUpsertFlow.tryEmit(
SyncFolderUpsertData(
folderId = folderId,
revisionDate = ZonedDateTime.now(),
isUpdate = true,
),
)
}
mutableSyncFolderUpsertFlow.tryEmit(
SyncFolderUpsertData(
folderId = folderId,
revisionDate = ZonedDateTime.now(clock),
isUpdate = true,
),
)
coVerify(exactly = 1) {
folderService.getFolder(folderId)
vaultDiskSource.saveFolder(
userId = MOCK_USER_STATE.activeUserId,
folder = folder,
)
vaultDiskSource.saveFolder(userId = userId, folder = folder)
}
}