diff --git a/feature/item/view/src/main/kotlin/de/davis/keygo/feature/item/view/creditcard/ViewCreditCardContent.kt b/feature/item/view/src/main/kotlin/de/davis/keygo/feature/item/view/creditcard/ViewCreditCardContent.kt index 5482ecf28..b5e40dde5 100644 --- a/feature/item/view/src/main/kotlin/de/davis/keygo/feature/item/view/creditcard/ViewCreditCardContent.kt +++ b/feature/item/view/src/main/kotlin/de/davis/keygo/feature/item/view/creditcard/ViewCreditCardContent.kt @@ -446,7 +446,8 @@ private fun ViewCreditCardContentPreview() { cardNumber = ObfuscatedString( "4111111111111111", formatted = "4111 1111 1111 1111", - visibleSuffixDigits = 4 + visibleSuffixChars = 4, + preservedChars = setOf(' '), ), cvv = ObfuscatedString("123"), expirationDate = "12/26", diff --git a/feature/item/view/src/main/kotlin/de/davis/keygo/feature/item/view/creditcard/ViewCreditCardViewModel.kt b/feature/item/view/src/main/kotlin/de/davis/keygo/feature/item/view/creditcard/ViewCreditCardViewModel.kt index d59145331..e16cdea17 100644 --- a/feature/item/view/src/main/kotlin/de/davis/keygo/feature/item/view/creditcard/ViewCreditCardViewModel.kt +++ b/feature/item/view/src/main/kotlin/de/davis/keygo/feature/item/view/creditcard/ViewCreditCardViewModel.kt @@ -84,7 +84,8 @@ internal class ViewCreditCardViewModel( ObfuscatedString( raw = raw, formatted = cardFormatter.formatNumber(raw), - visibleSuffixDigits = VISIBLE_CARD_NUMBER_SUFFIX, + visibleSuffixChars = VISIBLE_CARD_NUMBER_SUFFIX, + preservedChars = setOf(' '), ) } } diff --git a/feature/item/view/src/main/kotlin/de/davis/keygo/feature/item/view/login/model/ObfuscatedString.kt b/feature/item/view/src/main/kotlin/de/davis/keygo/feature/item/view/login/model/ObfuscatedString.kt index 14dc36193..730998e06 100644 --- a/feature/item/view/src/main/kotlin/de/davis/keygo/feature/item/view/login/model/ObfuscatedString.kt +++ b/feature/item/view/src/main/kotlin/de/davis/keygo/feature/item/view/login/model/ObfuscatedString.kt @@ -3,18 +3,20 @@ package de.davis.keygo.feature.item.view.login.model data class ObfuscatedString( val raw: String, val formatted: String = raw, - val visibleSuffixDigits: Int = 0, + val visibleSuffixChars: Int = 0, + val preservedChars: Set = emptySet(), ) { val hidden: String by lazy { - val totalDigits = formatted.count(Char::isDigit) - val reveal = if (totalDigits > visibleSuffixDigits) visibleSuffixDigits else 0 - var digitsSeen = 0 + val totalChars = formatted.count { it !in preservedChars } + val reveal = if (totalChars > visibleSuffixChars) visibleSuffixChars else 0 + var charsSeen = 0 buildString(formatted.length) { for (c in formatted) { - if (c.isDigit()) { - digitsSeen++ - append(if (totalDigits - digitsSeen < reveal) c else DEFAULT_OBFUSCATION_CHAR) - } else append(c) + if (c in preservedChars) append(c) + else { + charsSeen++ + append(if (totalChars - charsSeen < reveal) c else DEFAULT_OBFUSCATION_CHAR) + } } } } diff --git a/feature/item/view/src/test/kotlin/de/davis/keygo/feature/item/view/login/model/ObfuscatedStringTest.kt b/feature/item/view/src/test/kotlin/de/davis/keygo/feature/item/view/login/model/ObfuscatedStringTest.kt index 5fe10489d..9fae53e8e 100644 --- a/feature/item/view/src/test/kotlin/de/davis/keygo/feature/item/view/login/model/ObfuscatedStringTest.kt +++ b/feature/item/view/src/test/kotlin/de/davis/keygo/feature/item/view/login/model/ObfuscatedStringTest.kt @@ -18,11 +18,48 @@ class ObfuscatedStringTest { } @Test - fun `hidden preserves spaces from formatted string`() { - val obs = ObfuscatedString(raw = "4111111111111111", formatted = "4111 1111 1111 1111") + fun `hidden preserves spaces from formatted string when configured`() { + val obs = ObfuscatedString( + raw = "4111111111111111", + formatted = "4111 1111 1111 1111", + preservedChars = setOf(' '), + ) assertEquals(mask("**** **** **** ****"), obs.hidden) } + @Test + fun `hidden obfuscates spaces by default`() { + val obs = ObfuscatedString(raw = "4111111111111111", formatted = "4111 1111 1111 1111") + assertEquals(mask("****" + "*".repeat(15)), obs.hidden) + } + + @Test + fun `hidden obfuscates letters and symbols, not just digits`() { + val obs = ObfuscatedString("Sup3r\$ecret!") + assertEquals(mask("*".repeat(12)), obs.hidden) + } + + @Test + fun `hidden preserves only chars configured via preservedChars`() { + val obs = ObfuscatedString( + raw = "555-01-2345", + formatted = "555-01-2345", + preservedChars = setOf('-'), + ) + assertEquals(mask("***-**-****"), obs.hidden) + } + + @Test + fun `hidden reveals suffix among preserved chars for mixed content`() { + val obs = ObfuscatedString( + raw = "555-01-2345", + formatted = "555-01-2345", + visibleSuffixChars = 3, + preservedChars = setOf('-'), + ) + assertEquals(mask("***-**-*345"), obs.hidden) + } + @Test fun `raw is unaffected by formatted`() { val obs = ObfuscatedString(raw = "4111111111111111", formatted = "4111 1111 1111 1111") @@ -34,7 +71,8 @@ class ObfuscatedStringTest { val obs = ObfuscatedString( raw = "4111111111111234", formatted = "4111 1111 1111 1234", - visibleSuffixDigits = 4, + visibleSuffixChars = 4, + preservedChars = setOf(' '), ) assertEquals(mask("**** **** **** 1234"), obs.hidden) } @@ -44,7 +82,8 @@ class ObfuscatedStringTest { val obs = ObfuscatedString( raw = "378282246310005", formatted = "3782 822463 10005", - visibleSuffixDigits = 4, + visibleSuffixChars = 4, + preservedChars = setOf(' '), ) assertEquals(mask("**** ****** *0005"), obs.hidden) } @@ -54,7 +93,7 @@ class ObfuscatedStringTest { val obs = ObfuscatedString( raw = "1234", formatted = "1234", - visibleSuffixDigits = 4, + visibleSuffixChars = 4, ) assertEquals(mask("****"), obs.hidden) }