Skip to content

Commit e73f42b

Browse files
committed
test(flipcash): add tests for core utils, tokens, deeplinks, persistence, and userflags
- FormatUtils: round, format, formatWholeRoundDown, formatCurrency - Number: abbreviated() K/M/B/T suffixes - AggregationType: LTTB downsample and Bucketed aggregation types - DeeplinkError: fromCode mapping and enum coverage - TokenTypeConverters: JSON roundtrips for SocialLinks, BillCustomizations, HolderMetrics - ResolvedFlag/FieldOverride: effectiveValue and override logic
1 parent c1b4b5d commit e73f42b

8 files changed

Lines changed: 686 additions & 0 deletions

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.flipcash.app.core.money
2+
3+
import org.junit.Test
4+
import java.util.Locale
5+
import kotlin.test.assertEquals
6+
7+
class FormatUtilsTest {
8+
9+
@Test
10+
fun `round 1_005 rounds up to 1_01`() {
11+
// Note: 1.005 in IEEE 754 is slightly less than 1.005,
12+
// so (1.005 * 100.0).roundToInt() == 100 → 1.0
13+
// This documents the actual floating-point behavior.
14+
val result = FormatUtils.round(1.005)
15+
assertEquals(1.0, result)
16+
}
17+
18+
@Test
19+
fun `round 1_004 truncates to 1_0`() {
20+
assertEquals(1.0, FormatUtils.round(1.004))
21+
}
22+
23+
@Test
24+
fun `round 1_999 rounds to 2_0`() {
25+
assertEquals(2.0, FormatUtils.round(1.999))
26+
}
27+
28+
@Test
29+
fun `round 0_0 stays 0_0`() {
30+
assertEquals(0.0, FormatUtils.round(0.0))
31+
}
32+
33+
@Test
34+
fun `format 1234_56 returns comma-separated with two decimals`() {
35+
assertEquals("1,234.56", FormatUtils.format(1234.56))
36+
}
37+
38+
@Test
39+
fun `format 0_0 returns 0_00`() {
40+
assertEquals("0.00", FormatUtils.format(0.0))
41+
}
42+
43+
@Test
44+
fun `formatWholeRoundDown 1234_99 returns 1,234`() {
45+
assertEquals("1,234", FormatUtils.formatWholeRoundDown(1234.99))
46+
}
47+
48+
@Test
49+
fun `formatWholeRoundDown 0_1 returns 0`() {
50+
assertEquals("0", FormatUtils.formatWholeRoundDown(0.1))
51+
}
52+
53+
@Test
54+
fun `formatCurrency 10_50 with US locale returns dollar format`() {
55+
assertEquals("$10.50", FormatUtils.formatCurrency(10.50, Locale.US))
56+
}
57+
58+
@Test
59+
fun `1000 withCommas returns 1,000`() {
60+
assertEquals("1,000", 1000.withCommas())
61+
}
62+
63+
@Test
64+
fun `1000000 withCommas returns 1,000,000`() {
65+
assertEquals("1,000,000", 1000000.withCommas())
66+
}
67+
68+
@Test
69+
fun `999 withCommas returns 999`() {
70+
assertEquals("999", 999.withCommas())
71+
}
72+
73+
@Test
74+
fun `0 withCommas returns 0`() {
75+
assertEquals("0", 0.withCommas())
76+
}
77+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.flipcash.app.core.util
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertEquals
5+
6+
class NumberTest {
7+
8+
@Test
9+
fun belowThousand() {
10+
assertEquals("999", 999.abbreviated())
11+
}
12+
13+
@Test
14+
fun exactlyThousand() {
15+
assertEquals("1K", 1000.abbreviated())
16+
}
17+
18+
@Test
19+
fun thousandsWithDecimals() {
20+
assertEquals("1.5K", 1500.abbreviated())
21+
}
22+
23+
@Test
24+
fun thousandsTrimsTrailingZeros() {
25+
assertEquals("2K", 2000.abbreviated())
26+
}
27+
28+
@Test
29+
fun exactlyMillion() {
30+
assertEquals("1M", 1_000_000.abbreviated())
31+
}
32+
33+
@Test
34+
fun millionsWithDecimals() {
35+
assertEquals("2.5M", 2_500_000.abbreviated())
36+
}
37+
38+
@Test
39+
fun exactlyBillion() {
40+
assertEquals("1B", 1_000_000_000.abbreviated())
41+
}
42+
43+
@Test
44+
fun billionsWithDecimals() {
45+
assertEquals("1.23B", 1_230_000_000L.abbreviated())
46+
}
47+
48+
@Test
49+
fun exactlyTrillion() {
50+
assertEquals("1T", 1_000_000_000_000L.abbreviated())
51+
}
52+
53+
@Test
54+
fun zero() {
55+
assertEquals("0", 0.abbreviated())
56+
}
57+
58+
@Test
59+
fun smallNumber() {
60+
assertEquals("42", 42.abbreviated())
61+
}
62+
63+
@Test
64+
fun doubleInput() {
65+
assertEquals("1.5K", 1500.0.abbreviated())
66+
}
67+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package com.flipcash.app.onramp
2+
3+
import org.junit.Test
4+
import kotlin.test.assertEquals
5+
import kotlin.test.assertTrue
6+
7+
class DeeplinkErrorTest {
8+
9+
@Test
10+
fun `fromCode Long - Disconnected`() {
11+
assertEquals(DeeplinkError.Disconnected, DeeplinkError.fromCode(4900L))
12+
}
13+
14+
@Test
15+
fun `fromCode Long - Unauthorized`() {
16+
assertEquals(DeeplinkError.Unauthorized, DeeplinkError.fromCode(4100L))
17+
}
18+
19+
@Test
20+
fun `fromCode Long - UserRejectedRequest`() {
21+
assertEquals(DeeplinkError.UserRejectedRequest, DeeplinkError.fromCode(4001L))
22+
}
23+
24+
@Test
25+
fun `fromCode Long - InvalidInput`() {
26+
assertEquals(DeeplinkError.InvalidInput, DeeplinkError.fromCode(-32000L))
27+
}
28+
29+
@Test
30+
fun `fromCode Long - RequestedResourceNotAvailable`() {
31+
assertEquals(DeeplinkError.RequestedResourceNotAvailable, DeeplinkError.fromCode(-32002L))
32+
}
33+
34+
@Test
35+
fun `fromCode Long - TransactionRejected`() {
36+
assertEquals(DeeplinkError.TransactionRejected, DeeplinkError.fromCode(-32003L))
37+
}
38+
39+
@Test
40+
fun `fromCode Long - MethodNotFound`() {
41+
assertEquals(DeeplinkError.MethodNotFound, DeeplinkError.fromCode(-32601L))
42+
}
43+
44+
@Test
45+
fun `fromCode Long - InternalError`() {
46+
assertEquals(DeeplinkError.InternalError, DeeplinkError.fromCode(-32603L))
47+
}
48+
49+
@Test
50+
fun `fromCode null Long returns Unknown`() {
51+
assertEquals(DeeplinkError.Unknown, DeeplinkError.fromCode(null as Long?))
52+
}
53+
54+
@Test
55+
fun `fromCode unrecognized Long returns Unknown`() {
56+
assertEquals(DeeplinkError.Unknown, DeeplinkError.fromCode(9999L))
57+
}
58+
59+
@Test
60+
fun `fromCode String - UserRejectedRequest`() {
61+
assertEquals(DeeplinkError.UserRejectedRequest, DeeplinkError.fromCode("4001"))
62+
}
63+
64+
@Test
65+
fun `fromCode non-numeric String returns Unknown`() {
66+
assertEquals(DeeplinkError.Unknown, DeeplinkError.fromCode("invalid"))
67+
}
68+
69+
@Test
70+
fun `fromCode null String returns Unknown`() {
71+
assertEquals(DeeplinkError.Unknown, DeeplinkError.fromCode(null as String?))
72+
}
73+
74+
@Test
75+
fun `DeeplinkOnRampError subtypes are Throwable`() {
76+
val errors: List<Throwable> = listOf(
77+
DeeplinkOnRampError.FailedToSendTransaction(message = "fail"),
78+
DeeplinkOnRampError.WalletProvidedError(
79+
error = DeeplinkError.Disconnected,
80+
message = "disconnected"
81+
),
82+
DeeplinkOnRampError.FailedToCreateTransaction(message = "fail"),
83+
DeeplinkOnRampError.DecryptionError(message = "fail"),
84+
)
85+
86+
errors.forEach { error ->
87+
assertTrue(error is Throwable, "${error::class.simpleName} should be Throwable")
88+
}
89+
}
90+
91+
@Test
92+
fun `WalletProvidedError code matches error code`() {
93+
val error = DeeplinkOnRampError.WalletProvidedError(
94+
error = DeeplinkError.UserRejectedRequest,
95+
message = "rejected"
96+
)
97+
98+
assertEquals(DeeplinkError.UserRejectedRequest.code, error.code)
99+
assertEquals(4001L, error.code)
100+
}
101+
}

apps/flipcash/shared/persistence/db/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ android {
1212
}
1313

1414
dependencies {
15+
testImplementation(kotlin("test"))
16+
1517
implementation(libs.kotlinx.serialization.core)
1618
implementation(libs.kotlinx.serialization.json)
1719

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.flipcash.app.persistence.converters
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertEquals
5+
import kotlin.test.assertNull
6+
7+
class TokenTypeConvertersTest {
8+
9+
private val converter = TokenTypeConverters()
10+
11+
// region SocialLinks
12+
13+
@Test
14+
fun `fromSocialLinks and toSocialLinks roundtrip single website`() {
15+
val original = listOf(SocialLinkSerialized.Website(url = "https://example.com"))
16+
val serialized = converter.toSocialLinks(original)
17+
val deserialized = converter.fromSocialLinks(serialized)
18+
assertEquals(original, deserialized)
19+
}
20+
21+
@Test
22+
fun `fromSocialLinks and toSocialLinks roundtrip mixed links`() {
23+
val original = listOf(
24+
SocialLinkSerialized.Website(url = "https://example.com"),
25+
SocialLinkSerialized.X(username = "handle"),
26+
SocialLinkSerialized.Telegram(username = "tguser"),
27+
SocialLinkSerialized.Discord(inviteCode = "abc123"),
28+
)
29+
val serialized = converter.toSocialLinks(original)
30+
val deserialized = converter.fromSocialLinks(serialized)
31+
assertEquals(original, deserialized)
32+
}
33+
34+
@Test
35+
fun `fromSocialLinks returns null for null input`() {
36+
assertNull(converter.fromSocialLinks(null))
37+
}
38+
39+
@Test
40+
fun `toSocialLinks returns null for null input`() {
41+
assertNull(converter.toSocialLinks(null))
42+
}
43+
44+
@Test
45+
fun `fromSocialLinks and toSocialLinks roundtrip empty list`() {
46+
val original = emptyList<SocialLinkSerialized>()
47+
val serialized = converter.toSocialLinks(original)
48+
val deserialized = converter.fromSocialLinks(serialized)
49+
assertEquals(original, deserialized)
50+
}
51+
52+
// endregion
53+
54+
// region BillCustomizations
55+
56+
@Test
57+
fun `fromBillCustomizations and toBillCustomizations roundtrip solid background no texture`() {
58+
val original = BillCustomizationsSerialized(
59+
background = BillBackgroundSerialized.Solid(colorHex = "#FF0000"),
60+
texture = null,
61+
icon = null,
62+
)
63+
val serialized = converter.toBillCustomizations(original)
64+
val deserialized = converter.fromBillCustomizations(serialized)
65+
assertEquals(original, deserialized)
66+
}
67+
68+
@Test
69+
fun `fromBillCustomizations and toBillCustomizations roundtrip gradient with texture`() {
70+
val original = BillCustomizationsSerialized(
71+
background = BillBackgroundSerialized.Gradient(colors = listOf("#FF0000", "#00FF00", "#0000FF")),
72+
texture = BillTextureSerialized(index = 2, blendMode = "overlay", strength = 0.75f),
73+
icon = "aWNvbg==",
74+
)
75+
val serialized = converter.toBillCustomizations(original)
76+
val deserialized = converter.fromBillCustomizations(serialized)
77+
assertEquals(original, deserialized)
78+
}
79+
80+
@Test
81+
fun `fromBillCustomizations returns null for null input`() {
82+
assertNull(converter.fromBillCustomizations(null))
83+
}
84+
85+
@Test
86+
fun `toBillCustomizations returns null for null input`() {
87+
assertNull(converter.toBillCustomizations(null))
88+
}
89+
90+
// endregion
91+
92+
// region HolderMetrics
93+
94+
@Test
95+
fun `fromHolderMetrics and toHolderMetrics roundtrip with deltas`() {
96+
val original = HolderMetricsSerialized(
97+
currentHolders = 1500L,
98+
deltas = listOf(
99+
HolderDeltaSerialized(range = "DAY", delta = 10L),
100+
HolderDeltaSerialized(range = "WEEK", delta = -25L),
101+
),
102+
)
103+
val serialized = converter.toHolderMetrics(original)
104+
val deserialized = converter.fromHolderMetrics(serialized)
105+
assertEquals(original, deserialized)
106+
}
107+
108+
@Test
109+
fun `fromHolderMetrics and toHolderMetrics roundtrip empty deltas`() {
110+
val original = HolderMetricsSerialized(
111+
currentHolders = 0L,
112+
deltas = emptyList(),
113+
)
114+
val serialized = converter.toHolderMetrics(original)
115+
val deserialized = converter.fromHolderMetrics(serialized)
116+
assertEquals(original, deserialized)
117+
}
118+
119+
@Test
120+
fun `fromHolderMetrics returns null for null input`() {
121+
assertNull(converter.fromHolderMetrics(null))
122+
}
123+
124+
@Test
125+
fun `toHolderMetrics returns null for null input`() {
126+
assertNull(converter.toHolderMetrics(null))
127+
}
128+
129+
// endregion
130+
}

0 commit comments

Comments
 (0)