Skip to content

Commit 6db4165

Browse files
PM-19234 propagates attachment result errors to UI (#4869)
1 parent 18ce45e commit 6db4165

File tree

8 files changed

+62
-29
lines changed

8 files changed

+62
-29
lines changed

app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt

+6-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.bitwarden.vault.AttachmentView
66
import com.bitwarden.vault.Cipher
77
import com.bitwarden.vault.CipherView
88
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
9+
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
910
import com.x8bit.bitwarden.data.platform.manager.ReviewPromptManager
1011
import com.x8bit.bitwarden.data.platform.util.asFailure
1112
import com.x8bit.bitwarden.data.platform.util.asSuccess
@@ -152,15 +153,15 @@ class CipherManagerImpl(
152153
)
153154
.fold(
154155
onSuccess = { DeleteAttachmentResult.Success },
155-
onFailure = { DeleteAttachmentResult.Error },
156+
onFailure = { DeleteAttachmentResult.Error(error = it) },
156157
)
157158

158159
private suspend fun deleteCipherAttachmentForResult(
159160
cipherId: String,
160161
attachmentId: String,
161162
cipherView: CipherView,
162163
): Result<Cipher> {
163-
val userId = activeUserId ?: return IllegalStateException("No active user").asFailure()
164+
val userId = activeUserId ?: return NoActiveUserException().asFailure()
164165
return ciphersService
165166
.deleteCipherAttachment(
166167
cipherId = cipherId,
@@ -320,7 +321,7 @@ class CipherManagerImpl(
320321
fileUri = fileUri,
321322
)
322323
.fold(
323-
onFailure = { CreateAttachmentResult.Error },
324+
onFailure = { CreateAttachmentResult.Error(error = it) },
324325
onSuccess = { CreateAttachmentResult.Success(cipherView = it) },
325326
)
326327

@@ -332,7 +333,7 @@ class CipherManagerImpl(
332333
fileName: String?,
333334
fileUri: Uri,
334335
): Result<CipherView> {
335-
val userId = activeUserId ?: return IllegalStateException("No active user").asFailure()
336+
val userId = activeUserId ?: return NoActiveUserException().asFailure()
336337
val attachmentView = AttachmentView(
337338
id = null,
338339
url = null,
@@ -411,7 +412,7 @@ class CipherManagerImpl(
411412
cipherView: CipherView,
412413
attachmentId: String,
413414
): Result<File> {
414-
val userId = activeUserId ?: return IllegalStateException("No active user").asFailure()
415+
val userId = activeUserId ?: return NoActiveUserException().asFailure()
415416

416417
val cipher = cipherView
417418
.encryptCipherAndCheckForMigration(

app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/CreateAttachmentResult.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ sealed class CreateAttachmentResult {
1717
/**
1818
* Generic error while creating an attachment.
1919
*/
20-
data object Error : CreateAttachmentResult()
20+
data class Error(val error: Throwable) : CreateAttachmentResult()
2121
}

app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/DeleteAttachmentResult.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ sealed class DeleteAttachmentResult {
1313
/**
1414
* Generic error while deleting an attachment.
1515
*/
16-
data object Error : DeleteAttachmentResult()
16+
data class Error(val error: Throwable) : DeleteAttachmentResult()
1717
}

app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/attachments/AttachmentsScreen.kt

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ private fun AttachmentsDialogs(
125125
title = dialogState.title?.invoke(),
126126
message = dialogState.message(),
127127
onDismissRequest = onDismissRequest,
128+
throwable = dialogState.throwable,
128129
)
129130

130131
is AttachmentsState.DialogState.Loading -> BitwardenLoadingDialog(

app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/attachments/AttachmentsViewModel.kt

+7-4
Original file line numberDiff line numberDiff line change
@@ -265,13 +265,14 @@ class AttachmentsViewModel @Inject constructor(
265265
private fun handleCreateAttachmentResultReceive(
266266
action: AttachmentsAction.Internal.CreateAttachmentResultReceive,
267267
) {
268-
when (action.result) {
269-
CreateAttachmentResult.Error -> {
268+
when (val result = action.result) {
269+
is CreateAttachmentResult.Error -> {
270270
mutableStateFlow.update {
271271
it.copy(
272272
dialogState = AttachmentsState.DialogState.Error(
273273
title = R.string.an_error_has_occurred.asText(),
274274
message = R.string.generic_error_message.asText(),
275+
throwable = result.error,
275276
),
276277
)
277278
}
@@ -285,13 +286,14 @@ class AttachmentsViewModel @Inject constructor(
285286
}
286287

287288
private fun handleDeleteResultReceive(action: AttachmentsAction.Internal.DeleteResultReceive) {
288-
when (action.result) {
289-
DeleteAttachmentResult.Error -> {
289+
when (val result = action.result) {
290+
is DeleteAttachmentResult.Error -> {
290291
mutableStateFlow.update {
291292
it.copy(
292293
dialogState = AttachmentsState.DialogState.Error(
293294
title = R.string.an_error_has_occurred.asText(),
294295
message = R.string.generic_error_message.asText(),
296+
throwable = result.error,
295297
),
296298
)
297299
}
@@ -399,6 +401,7 @@ data class AttachmentsState(
399401
data class Error(
400402
val title: Text?,
401403
val message: Text,
404+
val throwable: Throwable? = null,
402405
) : DialogState()
403406

404407
/**

app/src/test/java/com/x8bit/bitwarden/data/vault/manager/CipherManagerTest.kt

+34-16
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
99
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
1010
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
1111
import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource
12+
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
1213
import com.x8bit.bitwarden.data.platform.manager.ReviewPromptManager
1314
import com.x8bit.bitwarden.data.platform.util.asFailure
1415
import com.x8bit.bitwarden.data.platform.util.asSuccess
@@ -47,8 +48,10 @@ import io.mockk.coVerify
4748
import io.mockk.every
4849
import io.mockk.just
4950
import io.mockk.mockk
51+
import io.mockk.mockkConstructor
5052
import io.mockk.mockkStatic
5153
import io.mockk.runs
54+
import io.mockk.unmockkConstructor
5255
import io.mockk.unmockkStatic
5356
import io.mockk.verify
5457
import kotlinx.coroutines.test.runTest
@@ -93,12 +96,17 @@ class CipherManagerTest {
9396
@BeforeEach
9497
fun setup() {
9598
mockkStatic(Uri::class)
99+
mockkConstructor(NoActiveUserException::class)
100+
every {
101+
anyConstructed<NoActiveUserException>() == any<NoActiveUserException>()
102+
} returns true
96103
}
97104

98105
@AfterEach
99106
fun tearDown() {
100107
unmockkStatic(Uri::class, Instant::class)
101108
unmockkStatic(Cipher::toEncryptedNetworkCipherResponse)
109+
unmockkConstructor(NoActiveUserException::class)
102110
}
103111

104112
@Test
@@ -607,7 +615,10 @@ class CipherManagerTest {
607615
cipherView = mockk(),
608616
)
609617

610-
assertEquals(DeleteAttachmentResult.Error, result)
618+
assertEquals(
619+
DeleteAttachmentResult.Error(error = NoActiveUserException()),
620+
result,
621+
)
611622
}
612623

613624
@Suppress("MaxLineLength")
@@ -617,20 +628,21 @@ class CipherManagerTest {
617628
fakeAuthDiskSource.userState = MOCK_USER_STATE
618629
val cipherId = "mockId-1"
619630
val attachmentId = "mockId-1"
631+
val error = Throwable("Fail")
620632
coEvery {
621633
ciphersService.deleteCipherAttachment(
622634
cipherId = cipherId,
623635
attachmentId = attachmentId,
624636
)
625-
} returns Throwable("Fail").asFailure()
637+
} returns error.asFailure()
626638

627639
val result = cipherManager.deleteCipherAttachment(
628640
cipherId = cipherId,
629641
attachmentId = attachmentId,
630642
cipherView = createMockCipherView(number = 1),
631643
)
632644

633-
assertEquals(DeleteAttachmentResult.Error, result)
645+
assertEquals(DeleteAttachmentResult.Error(error = error), result)
634646
}
635647

636648
@Suppress("MaxLineLength")
@@ -1138,7 +1150,7 @@ class CipherManagerTest {
11381150
fileUri = mockk(),
11391151
)
11401152

1141-
assertEquals(CreateAttachmentResult.Error, result)
1153+
assertEquals(CreateAttachmentResult.Error(NoActiveUserException()), result)
11421154
}
11431155

11441156
@Suppress("MaxLineLength")
@@ -1152,9 +1164,10 @@ class CipherManagerTest {
11521164
val mockCipherView = createMockCipherView(number = 1)
11531165
val mockFileName = "mockFileName-1"
11541166
val mockFileSize = "1"
1167+
val error = Throwable("Fail")
11551168
coEvery {
11561169
vaultSdkSource.encryptCipher(userId = userId, cipherView = mockCipherView)
1157-
} returns Throwable("Fail").asFailure()
1170+
} returns error.asFailure()
11581171

11591172
val result = cipherManager.createAttachment(
11601173
cipherId = cipherId,
@@ -1164,7 +1177,7 @@ class CipherManagerTest {
11641177
fileUri = mockUri,
11651178
)
11661179

1167-
assertEquals(CreateAttachmentResult.Error, result)
1180+
assertEquals(CreateAttachmentResult.Error(error = error), result)
11681181
}
11691182

11701183
@Suppress("MaxLineLength")
@@ -1186,6 +1199,7 @@ class CipherManagerTest {
11861199
url = null,
11871200
key = null,
11881201
)
1202+
val error = Throwable("Fail")
11891203
coEvery {
11901204
vaultSdkSource.encryptCipher(userId = userId, cipherView = mockCipherView)
11911205
} returns mockCipher.asSuccess()
@@ -1200,7 +1214,7 @@ class CipherManagerTest {
12001214
decryptedFilePath = mockFile.absolutePath,
12011215
encryptedFilePath = "${mockFile.absolutePath}.enc",
12021216
)
1203-
} returns Throwable("Fail").asFailure()
1217+
} returns error.asFailure()
12041218

12051219
val result = cipherManager.createAttachment(
12061220
cipherId = cipherId,
@@ -1210,7 +1224,7 @@ class CipherManagerTest {
12101224
fileUri = mockUri,
12111225
)
12121226

1213-
assertEquals(CreateAttachmentResult.Error, result)
1227+
assertEquals(CreateAttachmentResult.Error(error = error), result)
12141228
}
12151229

12161230
@Suppress("MaxLineLength")
@@ -1225,12 +1239,13 @@ class CipherManagerTest {
12251239
val mockCipher = createMockSdkCipher(number = 1, clock = clock)
12261240
val mockFileName = "mockFileName-1"
12271241
val mockFileSize = "1"
1242+
val error = Throwable("Fail")
12281243
coEvery {
12291244
vaultSdkSource.encryptCipher(userId = userId, cipherView = mockCipherView)
12301245
} returns mockCipher.asSuccess()
12311246
coEvery {
12321247
fileManager.writeUriToCache(fileUri = mockUri)
1233-
} returns Throwable("Fail").asFailure()
1248+
} returns error.asFailure()
12341249

12351250
val result = cipherManager.createAttachment(
12361251
cipherId = cipherId,
@@ -1240,7 +1255,7 @@ class CipherManagerTest {
12401255
fileUri = mockUri,
12411256
)
12421257

1243-
assertEquals(CreateAttachmentResult.Error, result)
1258+
assertEquals(CreateAttachmentResult.Error(error = error), result)
12441259
}
12451260

12461261
@Suppress("MaxLineLength")
@@ -1263,6 +1278,7 @@ class CipherManagerTest {
12631278
)
12641279
val mockFile = File.createTempFile("mockFile", "temp")
12651280
val mockAttachment = createMockSdkAttachment(number = 1)
1281+
val error = Throwable("Fail")
12661282
coEvery {
12671283
vaultSdkSource.encryptCipher(userId = userId, cipherView = mockCipherView)
12681284
} returns mockCipher.asSuccess()
@@ -1287,7 +1303,7 @@ class CipherManagerTest {
12871303
fileSize = mockFileSize,
12881304
),
12891305
)
1290-
} returns Throwable("Fail").asFailure()
1306+
} returns error.asFailure()
12911307

12921308
val result = cipherManager.createAttachment(
12931309
cipherId = cipherId,
@@ -1297,7 +1313,7 @@ class CipherManagerTest {
12971313
fileUri = mockUri,
12981314
)
12991315

1300-
assertEquals(CreateAttachmentResult.Error, result)
1316+
assertEquals(CreateAttachmentResult.Error(error = error), result)
13011317
}
13021318

13031319
@Suppress("MaxLineLength")
@@ -1321,6 +1337,7 @@ class CipherManagerTest {
13211337
val mockFile = File.createTempFile("mockFile", "temp")
13221338
val mockAttachment = createMockSdkAttachment(number = 1)
13231339
val mockAttachmentJsonResponse = createMockAttachmentJsonResponse(number = 1)
1340+
val error = Throwable("Fail")
13241341
coEvery {
13251342
vaultSdkSource.encryptCipher(userId = userId, cipherView = mockCipherView)
13261343
} returns mockCipher.asSuccess()
@@ -1351,7 +1368,7 @@ class CipherManagerTest {
13511368
attachmentJsonResponse = mockAttachmentJsonResponse,
13521369
encryptedFile = File("${mockFile.absoluteFile}.enc"),
13531370
)
1354-
} returns Throwable("Fail").asFailure()
1371+
} returns error.asFailure()
13551372

13561373
val result = cipherManager.createAttachment(
13571374
cipherId = cipherId,
@@ -1361,7 +1378,7 @@ class CipherManagerTest {
13611378
fileUri = mockUri,
13621379
)
13631380

1364-
assertEquals(CreateAttachmentResult.Error, result)
1381+
assertEquals(CreateAttachmentResult.Error(error = error), result)
13651382
}
13661383

13671384
@Suppress("MaxLineLength")
@@ -1389,6 +1406,7 @@ class CipherManagerTest {
13891406
val mockUpdatedCipherResponse = createMockCipher(number = 1).copy(
13901407
collectionIds = listOf("mockId-1"),
13911408
)
1409+
val error = Throwable("Fail")
13921410
coEvery {
13931411
vaultSdkSource.encryptCipher(userId = userId, cipherView = mockCipherView)
13941412
} returns mockCipher.asSuccess()
@@ -1428,7 +1446,7 @@ class CipherManagerTest {
14281446
userId = userId,
14291447
cipher = mockUpdatedCipherResponse.toEncryptedSdkCipher(),
14301448
)
1431-
} returns Throwable("Fail").asFailure()
1449+
} returns error.asFailure()
14321450

14331451
val result = cipherManager.createAttachment(
14341452
cipherId = cipherId,
@@ -1438,7 +1456,7 @@ class CipherManagerTest {
14381456
fileUri = mockUri,
14391457
)
14401458

1441-
assertEquals(CreateAttachmentResult.Error, result)
1459+
assertEquals(CreateAttachmentResult.Error(error = error), result)
14421460
}
14431461

14441462
@Suppress("MaxLineLength")

app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/attachments/AttachmentsScreenTest.kt

+1
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ class AttachmentsScreenTest : BaseComposeTest() {
217217
dialogState = AttachmentsState.DialogState.Error(
218218
title = null,
219219
message = errorMessage.asText(),
220+
throwable = null,
220221
),
221222
)
222223
}

0 commit comments

Comments
 (0)