Repository: thunderbird/thunderbird-android (language: kotlin)

Issue:
The avatar mapping it too restrictive and should apply defaults
The current mapping logic for avatars is to restrictive and needs to provide defaults instead of throwing exceptions.


Discussion hints:
nan

Pull request: feat(avatar): relax the data mapper logic
Relax the data mapper logic by adding a default value instead of throwing exceptions

Fixes #9468

Test command: ./gradlew :feature:account:storage:legacy:testDebugUnitTest --tests "net.thunderbird.feature.account.storage.legacy.mapper.DefaultAccountAvatarDataMapperTest"

Fix patch:
diff --git a/feature/account/storage/legacy/src/main/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapper.kt b/feature/account/storage/legacy/src/main/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapper.kt
index 8a318981902..7bcd58a0524 100644
--- a/feature/account/storage/legacy/src/main/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapper.kt
+++ b/feature/account/storage/legacy/src/main/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapper.kt
@@ -10,16 +10,36 @@ class DefaultAccountAvatarDataMapper : AccountAvatarDataMapper {
     override fun toDomain(dto: AvatarDto): AccountAvatar {
         return when (dto.avatarType) {
             AvatarTypeDto.MONOGRAM -> AccountAvatar.Monogram(
-                value = dto.avatarMonogram ?: throw IllegalArgumentException("Monogram value is required"),
+                value = dto.avatarMonogram ?: DEFAULT_MONOGRAM,
             )
 
-            AvatarTypeDto.IMAGE -> AccountAvatar.Image(
-                uri = dto.avatarImageUri ?: throw IllegalArgumentException("Image URI is required"),
-            )
+            AvatarTypeDto.IMAGE -> {
+                val uri = dto.avatarImageUri
 
-            AvatarTypeDto.ICON -> AccountAvatar.Icon(
-                name = dto.avatarIconName ?: throw IllegalArgumentException("Icon type is required"),
-            )
+                if (uri.isNullOrEmpty()) {
+                    AccountAvatar.Monogram(
+                        value = DEFAULT_MONOGRAM,
+                    )
+                } else {
+                    AccountAvatar.Image(
+                        uri = uri,
+                    )
+                }
+            }
+
+            AvatarTypeDto.ICON -> {
+                val name = dto.avatarIconName
+
+                if (name.isNullOrEmpty()) {
+                    AccountAvatar.Monogram(
+                        value = DEFAULT_MONOGRAM,
+                    )
+                } else {
+                    AccountAvatar.Icon(
+                        name = name,
+                    )
+                }
+            }
         }
     }
 
@@ -35,4 +55,8 @@ class DefaultAccountAvatarDataMapper : AccountAvatarDataMapper {
             avatarIconName = if (domain is AccountAvatar.Icon) domain.name else null,
         )
     }
+
+    private companion object {
+        const val DEFAULT_MONOGRAM = "XX"
+    }
 }


Test patch:
diff --git a/feature/account/storage/legacy/src/test/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapperTest.kt b/feature/account/storage/legacy/src/test/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapperTest.kt
index 79a8bf239e6..2bc1fd42160 100644
--- a/feature/account/storage/legacy/src/test/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapperTest.kt
+++ b/feature/account/storage/legacy/src/test/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapperTest.kt
@@ -4,7 +4,6 @@ import assertk.assertThat
 import assertk.assertions.isEqualTo
 import assertk.assertions.isNull
 import kotlin.test.Test
-import kotlin.test.assertFailsWith
 import net.thunderbird.feature.account.profile.AccountAvatar
 import net.thunderbird.feature.account.storage.profile.AvatarDto
 import net.thunderbird.feature.account.storage.profile.AvatarTypeDto
@@ -35,7 +34,7 @@ class DefaultAccountAvatarDataMapperTest {
     }
 
     @Test
-    fun `toDomain should throw IllegalArgumentException for invalid AvatarDto`() {
+    fun `toDomain should return default monogram for invalid AvatarDto`() {
         val avatarTypeDtos = AvatarTypeDto.entries
 
         avatarTypeDtos.forEach { type ->
@@ -47,9 +46,11 @@ class DefaultAccountAvatarDataMapperTest {
                 avatarIconName = null,
             )
 
-            assertFailsWith<IllegalArgumentException> {
-                testSubject.toDomain(dto)
-            }
+            // Act
+            val result = testSubject.toDomain(dto)
+
+            // Assert
+            assertDomainMonogram(result, AccountAvatar.Monogram("XX"))
         }
     }
 
@@ -74,17 +75,17 @@ class DefaultAccountAvatarDataMapperTest {
         }
     }
 
-    private fun assertDomainMonogram(actual: AccountAvatar.Monogram, expected: AccountAvatar) {
+    private fun assertDomainMonogram(actual: AccountAvatar, expected: AccountAvatar) {
         require(expected is AccountAvatar.Monogram) { "Expected AccountAvatar to be of type Monogram" }
         assertThat(actual).isEqualTo(expected)
     }
 
-    private fun assertDomainImage(actual: AccountAvatar.Image, expected: AccountAvatar) {
+    private fun assertDomainImage(actual: AccountAvatar, expected: AccountAvatar) {
         require(expected is AccountAvatar.Image) { "Expected AccountAvatar to be of type Image" }
         assertThat(actual).isEqualTo(expected)
     }
 
-    private fun assertDomainIcon(actual: AccountAvatar.Icon, expected: AccountAvatar) {
+    private fun assertDomainIcon(actual: AccountAvatar, expected: AccountAvatar) {
         require(expected is AccountAvatar.Icon) { "Expected AccountAvatar to be of type Icon" }
         assertThat(actual).isEqualTo(expected)
     }