Skip to content

Commit 8852dff

Browse files
committed
Feat: AuthTokenSerializer 추가 및 테스트 코드 작성
- AuthTokenSerializer 클래스 추가 - TokenSerializer 인터페이스 추가 - AuthTokenSerializer 테스트 코드 작성
1 parent 397a912 commit 8852dff

4 files changed

Lines changed: 157 additions & 0 deletions

File tree

core/datastore/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ android {
99
}
1010

1111
dependencies {
12+
implementation(projects.core.security)
13+
1214
implementation(libs.androidx.datastore.preferences)
1315
implementation(libs.kotlinx.serialization.json)
16+
17+
testImplementation(libs.androidx.junit)
18+
testImplementation(libs.kotlin.coroutines.test)
1419
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.threegap.bitnagil.datastore.serializer
2+
3+
import com.threegap.bitnagil.datastore.model.AuthToken
4+
import com.threegap.bitnagil.security.crypto.Crypto
5+
import kotlinx.coroutines.Dispatchers
6+
import kotlinx.coroutines.withContext
7+
import kotlinx.serialization.json.Json
8+
import java.io.InputStream
9+
import java.io.OutputStream
10+
import java.util.Base64
11+
import javax.inject.Inject
12+
13+
internal class AuthTokenSerializer
14+
@Inject
15+
constructor(
16+
private val crypto: Crypto,
17+
) : TokenSerializer {
18+
override val defaultValue: AuthToken
19+
get() = AuthToken()
20+
21+
override suspend fun readFrom(input: InputStream): AuthToken {
22+
return try {
23+
val encryptedBytes =
24+
withContext(Dispatchers.IO) {
25+
input.use { it.readBytes() }
26+
}
27+
val decodedBytes = Base64.getDecoder().decode(encryptedBytes)
28+
val decryptedBytes = crypto.decrypt(decodedBytes)
29+
val decodedJsonString = decryptedBytes.decodeToString()
30+
Json.decodeFromString(decodedJsonString)
31+
} catch (e: Exception) {
32+
AuthToken()
33+
}
34+
}
35+
36+
override suspend fun writeTo(
37+
t: AuthToken,
38+
output: OutputStream,
39+
) {
40+
val json = Json.encodeToString(t)
41+
val bytes = json.toByteArray()
42+
val encryptedBytes = crypto.encrypt(bytes)
43+
val encryptedBytesBase64 = Base64.getEncoder().encode(encryptedBytes)
44+
withContext(Dispatchers.IO) {
45+
output.use {
46+
it.write(encryptedBytesBase64)
47+
}
48+
}
49+
}
50+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.threegap.bitnagil.datastore.serializer
2+
3+
import androidx.datastore.core.Serializer
4+
import com.threegap.bitnagil.datastore.model.AuthToken
5+
6+
interface TokenSerializer : Serializer<AuthToken>
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.threegap.bitnagil.datastore.serializer
2+
3+
import com.threegap.bitnagil.datastore.model.AuthToken
4+
import com.threegap.bitnagil.security.crypto.Crypto
5+
import kotlinx.coroutines.test.runTest
6+
import kotlinx.serialization.json.Json
7+
import org.junit.Assert.assertEquals
8+
import org.junit.Before
9+
import org.junit.Test
10+
import java.io.ByteArrayInputStream
11+
import java.io.ByteArrayOutputStream
12+
import java.util.Base64
13+
14+
class AuthTokenSerializerTest {
15+
private lateinit var serializer: AuthTokenSerializer
16+
private lateinit var crypto: FakeCrypto
17+
private lateinit var fakeToken: AuthToken
18+
private lateinit var encrypted: ByteArray
19+
private lateinit var json: String
20+
21+
class FakeCrypto(
22+
private val encryptResult: ByteArray,
23+
private val decryptResult: ByteArray,
24+
private val shouldFailDecrypt: Boolean = false,
25+
) : Crypto {
26+
override fun encrypt(bytes: ByteArray): ByteArray = encryptResult
27+
28+
override fun decrypt(bytes: ByteArray): ByteArray {
29+
if (shouldFailDecrypt) throw RuntimeException("복호화 실패")
30+
return decryptResult
31+
}
32+
}
33+
34+
@Before
35+
fun setUp() {
36+
fakeToken = AuthToken("access", "refresh")
37+
json = Json.encodeToString(fakeToken)
38+
encrypted = "암호화된값".toByteArray()
39+
40+
crypto =
41+
FakeCrypto(
42+
encryptResult = encrypted,
43+
decryptResult = json.toByteArray(),
44+
)
45+
46+
serializer = AuthTokenSerializer(crypto)
47+
}
48+
49+
@Test
50+
fun `writeTo는 토큰을 암호화해서 저장한다`() =
51+
runTest {
52+
// given
53+
val outputStream = ByteArrayOutputStream()
54+
55+
// when
56+
serializer.writeTo(fakeToken, outputStream)
57+
58+
// then
59+
val expected = Base64.getEncoder().encode(encrypted)
60+
assertEquals(expected.toList(), outputStream.toByteArray().toList())
61+
}
62+
63+
@Test
64+
fun `readFrom은 암호화된 데이터를 복호화하여 토큰으로 변환한다`() =
65+
runTest {
66+
// given
67+
val input = Base64.getEncoder().encode(encrypted)
68+
val inputStream = ByteArrayInputStream(input)
69+
70+
// when
71+
val result = serializer.readFrom(inputStream)
72+
73+
// then
74+
assertEquals(fakeToken, result)
75+
}
76+
77+
@Test
78+
fun `readFrom에서 예외 발생시 기본값을 반환한다`() =
79+
runTest {
80+
// given
81+
val brokenCrypto =
82+
FakeCrypto(
83+
encryptResult = byteArrayOf(),
84+
decryptResult = byteArrayOf(),
85+
shouldFailDecrypt = true,
86+
)
87+
val brokenSerializer = AuthTokenSerializer(brokenCrypto)
88+
val inputStream = ByteArrayInputStream(Base64.getEncoder().encode(encrypted))
89+
90+
// when
91+
val result = brokenSerializer.readFrom(inputStream)
92+
93+
// then
94+
assertEquals(AuthToken(), result)
95+
}
96+
}

0 commit comments

Comments
 (0)