Skip to content

Commit bde8cc8

Browse files
committed
test: add Robolectric tests for Base64, serializers, and DateUtils
These tests use Robolectric to handle android.util.Base64 and android.text.format.DateFormat dependencies that cannot run as pure JVM tests. - Base64ExtensionsTest: encode/decode roundtrips, URL-safe, base58 - SerializerTest: ByteList and PublicKey JSON serialization roundtrips - DateUtilsTest: date formatting and Instant conversion
1 parent e73f42b commit bde8cc8

6 files changed

Lines changed: 290 additions & 0 deletions

File tree

libs/datetime/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ android {
99
dependencies {
1010
api(libs.kotlinx.datetime)
1111

12+
testImplementation(kotlin("test"))
13+
testImplementation(libs.robolectric)
1214
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.getcode.util
2+
3+
import kotlinx.datetime.Instant
4+
import org.junit.runner.RunWith
5+
import org.robolectric.RobolectricTestRunner
6+
import org.robolectric.annotation.Config
7+
import kotlin.test.Test
8+
import kotlin.test.assertEquals
9+
import kotlin.test.assertTrue
10+
11+
@RunWith(RobolectricTestRunner::class)
12+
@Config(manifest = Config.NONE)
13+
class DateUtilsTest {
14+
15+
// --- getDate ---
16+
17+
@Test
18+
fun getDateFormatsWithDefaultPattern() {
19+
// Jan 15, 2023 at noon UTC
20+
val millis = 1673784000000L
21+
val result = DateUtils.getDate(millis)
22+
assertTrue(result.contains("2023"))
23+
}
24+
25+
@Test
26+
fun getDateFormatsYearOnly() {
27+
val millis = 1673784000000L
28+
val result = DateUtils.getDate(millis, "yyyy")
29+
assertEquals("2023", result)
30+
}
31+
32+
@Test
33+
fun getDateFormatsMonthDay() {
34+
// March 5, 2024 00:00 UTC
35+
val millis = 1709596800000L
36+
val result = DateUtils.getDate(millis, "MM-dd")
37+
// Should contain month and day
38+
assertTrue(result.matches(Regex("\\d{2}-\\d{2}")))
39+
}
40+
41+
@Test
42+
fun getDateFormatsTimeOnly() {
43+
val millis = 1673784000000L
44+
val result = DateUtils.getDate(millis, "h:mm aa")
45+
// Should contain time with AM/PM
46+
assertTrue(result.contains("AM") || result.contains("PM") ||
47+
result.contains("am") || result.contains("pm"),
48+
"Expected AM/PM time format but got: $result")
49+
}
50+
51+
// --- toInstantFromMillis ---
52+
53+
@Test
54+
fun toInstantFromMillis() {
55+
val millis = 1673784000000L
56+
val instant = millis.toInstantFromMillis()
57+
assertEquals(millis, instant.toEpochMilliseconds())
58+
}
59+
60+
@Test
61+
fun toInstantFromMillisZero() {
62+
val instant = 0L.toInstantFromMillis()
63+
assertEquals(Instant.fromEpochMilliseconds(0), instant)
64+
}
65+
66+
@Test
67+
fun toInstantFromMillisRoundtrip() {
68+
val now = kotlin.time.Clock.System.now()
69+
val millis = now.toEpochMilliseconds()
70+
val instant = millis.toInstantFromMillis()
71+
assertEquals(millis, instant.toEpochMilliseconds())
72+
}
73+
}

libs/encryption/keys/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@ dependencies {
1717
implementation(libs.kotlinx.serialization.json)
1818

1919
testImplementation(kotlin("test"))
20+
testImplementation(libs.robolectric)
21+
testImplementation(libs.kotlinx.serialization.json)
2022
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.getcode.utils.serializer
2+
3+
import com.getcode.solana.keys.PublicKey
4+
import kotlinx.serialization.Serializable
5+
import kotlinx.serialization.json.Json
6+
import org.junit.runner.RunWith
7+
import org.robolectric.RobolectricTestRunner
8+
import org.robolectric.annotation.Config
9+
import kotlin.test.Test
10+
import kotlin.test.assertEquals
11+
import kotlin.test.assertTrue
12+
13+
@RunWith(RobolectricTestRunner::class)
14+
@Config(manifest = Config.NONE)
15+
class SerializerTest {
16+
17+
@Serializable
18+
data class ByteListWrapper(
19+
@Serializable(with = ByteListAsBase64Serializer::class)
20+
val data: List<Byte>
21+
)
22+
23+
@Serializable
24+
data class PublicKeyWrapper(
25+
@Serializable(with = PublicKeyAsStringSerializer::class)
26+
val key: PublicKey
27+
)
28+
29+
// --- ByteListAsBase64Serializer ---
30+
31+
@Test
32+
fun byteListSerializerRoundtrip() {
33+
val original = ByteListWrapper(listOf(1, 2, 3, 4, 5, -128, 127))
34+
val json = Json.encodeToString(original)
35+
val decoded = Json.decodeFromString<ByteListWrapper>(json)
36+
assertEquals(original.data, decoded.data)
37+
}
38+
39+
@Test
40+
fun byteListSerializerEmptyList() {
41+
val original = ByteListWrapper(emptyList())
42+
val json = Json.encodeToString(original)
43+
val decoded = Json.decodeFromString<ByteListWrapper>(json)
44+
assertEquals(original.data, decoded.data)
45+
}
46+
47+
@Test
48+
fun byteListSerializerLargePayload() {
49+
val data = (0..255).map { it.toByte() }
50+
val original = ByteListWrapper(data)
51+
val json = Json.encodeToString(original)
52+
val decoded = Json.decodeFromString<ByteListWrapper>(json)
53+
assertEquals(original.data, decoded.data)
54+
}
55+
56+
@Test
57+
fun byteListSerializerProducesBase64String() {
58+
val original = ByteListWrapper(listOf(0, 0, 0))
59+
val json = Json.encodeToString(original)
60+
// Base64 of [0, 0, 0] is "AAAA"
61+
assertTrue(json.contains("AAAA"))
62+
}
63+
64+
// --- PublicKeyAsStringSerializer ---
65+
66+
@Test
67+
fun publicKeySerializerRoundtrip() {
68+
val bytes = ByteArray(32) { (it + 1).toByte() }
69+
val key = PublicKey(bytes.toList())
70+
val original = PublicKeyWrapper(key)
71+
val json = Json.encodeToString(original)
72+
val decoded = Json.decodeFromString<PublicKeyWrapper>(json)
73+
assertEquals(original.key, decoded.key)
74+
}
75+
76+
@Test
77+
fun publicKeySerializerProducesBase58String() {
78+
val bytes = ByteArray(32) { 1 }
79+
val key = PublicKey(bytes.toList())
80+
val original = PublicKeyWrapper(key)
81+
val json = Json.encodeToString(original)
82+
// Should contain a base58-encoded string (alphanumeric, no 0OIl)
83+
assertTrue(json.contains("\"key\""))
84+
}
85+
}

libs/encryption/utils/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ dependencies {
1313
implementation(libs.kotlinx.serialization.json)
1414

1515
testImplementation(kotlin("test"))
16+
testImplementation(libs.robolectric)
1617
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package com.getcode.utils
2+
3+
import org.junit.runner.RunWith
4+
import org.robolectric.RobolectricTestRunner
5+
import org.robolectric.annotation.Config
6+
import kotlin.test.Test
7+
import kotlin.test.assertEquals
8+
import kotlin.test.assertTrue
9+
10+
@RunWith(RobolectricTestRunner::class)
11+
@Config(manifest = Config.NONE)
12+
class Base64ExtensionsTest {
13+
14+
// --- ByteArray.base64 ---
15+
16+
@Test
17+
fun byteArrayBase64Roundtrip() {
18+
val data = byteArrayOf(1, 2, 3, 4, 5, 127, -128)
19+
val encoded = data.base64
20+
val decoded = encoded.decodeBase64()
21+
assertTrue(data.contentEquals(decoded))
22+
}
23+
24+
@Test
25+
fun byteArrayBase64EmptyArray() {
26+
val data = byteArrayOf()
27+
val encoded = data.base64
28+
val decoded = encoded.decodeBase64()
29+
assertTrue(data.contentEquals(decoded))
30+
}
31+
32+
// --- List<Byte>.base64 ---
33+
34+
@Test
35+
fun byteListBase64Roundtrip() {
36+
val data = listOf<Byte>(10, 20, 30, -1, -128)
37+
val encoded = data.base64
38+
val decoded = encoded.decodeBase64().toList()
39+
assertEquals(data, decoded)
40+
}
41+
42+
// --- encodeBase64 / decodeBase64 ---
43+
44+
@Test
45+
fun encodeBase64DefaultRoundtrip() {
46+
val data = "Hello, World!".toByteArray()
47+
val encoded = data.encodeBase64()
48+
val decoded = encoded.decodeBase64()
49+
assertTrue(data.contentEquals(decoded))
50+
}
51+
52+
@Test
53+
fun encodeBase64UrlSafe() {
54+
// URL-safe base64 uses - and _ instead of + and /
55+
val data = byteArrayOf(-1, -2, -3, -4) // bytes that produce + and / in standard base64
56+
val urlSafe = data.encodeBase64(urlSafe = true)
57+
assertTrue(!urlSafe.contains('+') && !urlSafe.contains('/'))
58+
}
59+
60+
@Test
61+
fun encodeBase64UrlSafeRoundtrip() {
62+
val data = byteArrayOf(0, 127, -128, 63, -1)
63+
val encoded = data.encodeBase64(urlSafe = true)
64+
// URL-safe decode: replace back and decode
65+
val standard = encoded.replace('-', '+').replace('_', '/')
66+
val decoded = standard.decodeBase64()
67+
assertTrue(data.contentEquals(decoded))
68+
}
69+
70+
// --- encodeBase64ToArray ---
71+
72+
@Test
73+
fun encodeBase64ToArrayRoundtrip() {
74+
val data = byteArrayOf(10, 20, 30, 40, 50)
75+
val encodedBytes = data.encodeBase64ToArray()
76+
val decoded = encodedBytes.decodeBase64()
77+
assertTrue(data.contentEquals(decoded))
78+
}
79+
80+
// --- String.decodeBase64 ---
81+
82+
@Test
83+
fun stringDecodeBase64() {
84+
val original = "Test string for Base64"
85+
val encoded = original.toByteArray().base64
86+
val decoded = encoded.decodeBase64()
87+
assertEquals(original, String(decoded, Charsets.UTF_8))
88+
}
89+
90+
// --- ByteArray.decodeBase64 (from ByteArray input) ---
91+
92+
@Test
93+
fun byteArrayDecodeBase64() {
94+
val data = byteArrayOf(1, 2, 3, 4)
95+
val encoded = data.encodeBase64ToArray()
96+
val decoded = encoded.decodeBase64()
97+
assertTrue(data.contentEquals(decoded))
98+
}
99+
100+
// --- Large data roundtrip ---
101+
102+
@Test
103+
fun largeDataRoundtrip() {
104+
val data = ByteArray(1024) { it.toByte() }
105+
val encoded = data.base64
106+
val decoded = encoded.decodeBase64()
107+
assertTrue(data.contentEquals(decoded))
108+
}
109+
110+
// --- base58 on List<Byte> and ByteArray ---
111+
112+
@Test
113+
fun byteListBase58Roundtrip() {
114+
val data = listOf<Byte>(1, 2, 3, 4, 5)
115+
val encoded = data.base58
116+
val decoded = encoded.decodeBase58()
117+
assertTrue(data.toByteArray().contentEquals(decoded))
118+
}
119+
120+
@Test
121+
fun byteArrayBase58Roundtrip() {
122+
val data = byteArrayOf(10, 20, 30)
123+
val encoded = data.base58
124+
val decoded = encoded.decodeBase58()
125+
assertTrue(data.contentEquals(decoded))
126+
}
127+
}

0 commit comments

Comments
 (0)