Updated on 2026-08-14
This commit is contained in:
commit
4dec0b44cd
3 changed files with 52 additions and 6 deletions
|
|
@ -100,9 +100,10 @@ internal class DefaultAddressBookRepository(
|
|||
writeMutex.withLock {
|
||||
val userWallet = findUserWallet(contact.walletId.stringValue)
|
||||
?: return@withLock AddressBookSyncError.Unknown.left()
|
||||
val current = currentContacts(contact.walletId, userWallet)
|
||||
val merged = current.filterNot { it.id == contact.id } + contact
|
||||
persist(userWallet, AddressBook(contacts = merged))
|
||||
currentContacts(contact.walletId, userWallet).flatMap { current ->
|
||||
val merged = current.filterNot { it.id == contact.id } + contact
|
||||
persist(userWallet, AddressBook(contacts = merged))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -181,9 +182,28 @@ internal class DefaultAddressBookRepository(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun currentContacts(userWalletId: UserWalletId, userWallet: UserWallet): List<Contact> {
|
||||
val blob = blobStore.getBlobSync(userWalletId) ?: return emptyList()
|
||||
return decryptContacts(blob, userWallet)
|
||||
/**
|
||||
* The contacts currently stored for [userWalletId], as the base a write is merged onto.
|
||||
*
|
||||
* A missing blob means no book exists yet → an empty base, so the first contact legitimately creates it.
|
||||
* But a blob that fails to decrypt must surface as [AddressBookSyncError.DecryptionFailed] rather than an empty
|
||||
* list: treating a broken book as empty would let a merged write overwrite the (non-empty) backend copy with a
|
||||
* book built from a single new contact, wiping every existing one.
|
||||
*/
|
||||
private suspend fun currentContacts(
|
||||
userWalletId: UserWalletId,
|
||||
userWallet: UserWallet,
|
||||
): Either<AddressBookSyncError, List<Contact>> {
|
||||
val blob = blobStore.getBlobSync(userWalletId) ?: return emptyList<Contact>().right()
|
||||
return cipher.decrypt(blob, userWallet)
|
||||
.map { it.contacts }
|
||||
.mapLeft { error ->
|
||||
logger.e(
|
||||
"Refusing to overwrite address book for wallet $userWalletId: it is stored but decrypt " +
|
||||
"failed with $error",
|
||||
)
|
||||
AddressBookSyncError.DecryptionFailed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -283,6 +283,25 @@ internal class DefaultAddressBookRepositoryTest {
|
|||
coVerify(exactly = 0) { blobStore.storeBlob(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN stored book that fails to decrypt WHEN saveContact THEN returns DecryptionFailed and pushes nothing`() =
|
||||
runTest {
|
||||
// Arrange
|
||||
val storedBlob = createBlob()
|
||||
coEvery { blobStore.getBlobSync(UserWalletId(WALLET_A)) } returns storedBlob
|
||||
every { cipher.decrypt(storedBlob, userWallet) } returns AddressBookCryptoError.DecryptionFailed.left()
|
||||
|
||||
// Act
|
||||
val result = repository.saveContact(createContact(id = "c2", name = "Bob"))
|
||||
|
||||
// Assert
|
||||
assertThat(result).isEqualTo(AddressBookSyncError.DecryptionFailed.left())
|
||||
// The broken book is left untouched: nothing is encrypted, pushed, or stored.
|
||||
coVerify(exactly = 0) { addressBookApi.updateAddressBook(any(), any(), any()) }
|
||||
coVerify(exactly = 0) { blobStore.storeBlob(any()) }
|
||||
coVerify(exactly = 0) { eTagsStore.store(any(), any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN existing contact id WHEN saveContact THEN replaces it`() = runTest {
|
||||
// Arrange
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue