-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[PC-256] 이용약관 페이지 UI 구현 #25
Changes from all commits
90b90e0
60bb46c
f906476
a0fa57a
2a1fb55
bda08b0
6c028dd
082bc81
77a7e84
c8b7b22
3098f31
8ee6aa2
3f6b1ab
a4845e4
8143032
62759f9
b34d8c2
854bdb0
db3ca77
526a328
9af33c8
f894dff
79afd27
5074087
f14ff20
2d236cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<network-security-config> | ||
<debug-overrides> | ||
<trust-anchors> | ||
<!-- Trust user added CAs while debuggable only --> | ||
<certificates src="user" /> | ||
<certificates src="system" /> | ||
</trust-anchors> | ||
</debug-overrides> | ||
|
||
<base-config cleartextTrafficPermitted="true"> | ||
<trust-anchors> | ||
<certificates src="system" /> | ||
<certificates src="user" /> | ||
</trust-anchors> | ||
</base-config> | ||
</network-security-config> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
plugins { | ||
id("piece.kotlin.library") | ||
id("piece.kotlin.hilt") | ||
} | ||
|
||
dependencies { | ||
implementation(libs.coroutines.core) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.puzzle.common | ||
|
||
import java.time.LocalDateTime | ||
import java.time.format.DateTimeParseException | ||
|
||
/** | ||
* String?을 LocalDateTime으로 변환합니다. | ||
* | ||
* - 문자열이 null인 경우, [LocalDateTime.MIN]을 반환합니다. | ||
* - 잘못된 형식으로 인해 파싱에 실패할 경우, [LocalDateTime.MIN]을 반환합니다. | ||
*/ | ||
fun String?.parseDateTime(): LocalDateTime { | ||
return try { | ||
this?.let { LocalDateTime.parse(it) } ?: LocalDateTime.MIN | ||
} catch (e: DateTimeParseException) { | ||
LocalDateTime.MIN | ||
} | ||
} | ||
Comment on lines
+12
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
fun String?.parseDateTime(): LocalDateTime {
return this?.let {
runCatching { LocalDateTime.parse(it) }
.getOrDefault(LocalDateTime.MIN)
} ?: LocalDateTime.MIN
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LocalDateTime.MIN 은 뭐가 나오나요?! 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
try-catch나 runCatching으로 묶는 것 모두 에러를 잡기위한 코드인데, Result객체를 사용하는 이유는 에러를 잡고 �이 책임을 다른 클래스에 위임하기 위해서 사용하는 것이라고 생각해요. 하지만, 위 코드는 해당 함수에서 에러를 잡고 바로 에러를 핸들링하기 때문에 오히려 코드 수도 늘어나고 불필요한 것 같습니다. runCatching을 사용하면 try-catch보다 직관적이지 않고 코드수도 늘어난다고 생각해요. 어떻게 생각하시나요?! 관련 레퍼런스 하나 드릴게요! 에러 핸들링을 다른 클래스에게 위임하기 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
처음 이렇게 설계를 했지만, 추후에 Room을 사용하는 Database 모듈에서 해당 로직이 가짜 중복이 아닌 정말 진짜 중복으로 재사용 됨을 발견하였습니다.
그래서 진짜 중복이 발생한 두 코드를 위해 각 모듈에 각각 배치하는 것 보다 어차피 추후에 만들어진 어떤가요?! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package com.puzzle.common | ||
|
||
import org.junit.Assert.assertEquals | ||
import org.junit.jupiter.api.Test | ||
import java.time.LocalDateTime | ||
|
||
class TimeUtilTest { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 허허 테스트 코드의 시작을...!!!! 👍 👍 |
||
|
||
@Test | ||
fun `올바른 형식의 문자열을 LocalDateTime으로 변환할 수 있다`() { | ||
// given | ||
val dateTimeString = "2024-06-01T00:00:00" | ||
val expected = LocalDateTime.parse(dateTimeString) | ||
|
||
// when | ||
val actual = dateTimeString.parseDateTime() | ||
|
||
// then | ||
assertEquals(expected, actual) | ||
} | ||
|
||
@Test | ||
fun `null 값을 파싱하려고 할 경우 LocalDateTime_MIN을 반환한다`() { | ||
// given | ||
val nullString: String? = null | ||
val expected = LocalDateTime.MIN | ||
|
||
// when | ||
val actual = nullString.parseDateTime() | ||
|
||
// then | ||
assertEquals(expected, actual) | ||
} | ||
|
||
@Test | ||
fun `형식에 맞지 않은 문자열을 파싱하려고 할 경우 LocalDateTime_MIN을 반환한다`() { | ||
// given | ||
val invalidString = "invalid-date-format" | ||
val expected = LocalDateTime.MIN | ||
|
||
// when | ||
val actual = invalidString.parseDateTime() | ||
|
||
// then | ||
assertEquals(expected, actual) | ||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.puzzle.data.repository | ||
|
||
import com.puzzle.database.model.terms.TermEntity | ||
import com.puzzle.database.source.term.LocalTermDataSource | ||
import com.puzzle.domain.model.terms.Term | ||
import com.puzzle.domain.repository.TermsRepository | ||
import com.puzzle.network.model.UNKNOWN_INT | ||
import com.puzzle.network.source.TermDataSource | ||
import javax.inject.Inject | ||
|
||
class TermsRepositoryImpl @Inject constructor( | ||
private val termDataSource: TermDataSource, | ||
private val localTermDataSource: LocalTermDataSource, | ||
) : TermsRepository { | ||
override suspend fun loadTerms(): Result<Unit> = runCatching { | ||
val terms = termDataSource.loadTerms() | ||
.getOrThrow() | ||
.toDomain() | ||
.filter { it.termId != UNKNOWN_INT } | ||
|
||
val termsEntity = terms.map { | ||
TermEntity( | ||
id = it.termId, | ||
title = it.title, | ||
content = it.content, | ||
required = it.required, | ||
startDate = it.startDate, | ||
) | ||
} | ||
|
||
localTermDataSource.clearAndInsertTerms(termsEntity) | ||
} | ||
|
||
override suspend fun getTerms(): Result<List<Term>> = runCatching { | ||
localTermDataSource.getTerms() | ||
.map { it.toDomain() } | ||
Comment on lines
+34
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
헉, 스코프하다라는 것이 어떤 뜻인가요?! 좀 더 광범위 ...? 음.. 해당 함수는 약관을 불러오는 함수를 뜻하는데요, 혹시 더 직관적이고 좋은 뜻이 있을까요 ?! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 앗 죄송합니다 표현이 명확하지 않았던 것 같아요! 제 말은 조금 더 좁은 범위, 의미를 가진 단어이면 좋을 것 같다는 뜻이었어요!! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오오 새로 배웁니다! 다음 PR에서 retrieve로 바꿔놓겠습니다! |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package com.puzzle.data.repository | ||
|
||
import com.puzzle.database.source.term.LocalTermDataSource | ||
import com.puzzle.network.model.UNKNOWN_INT | ||
import com.puzzle.network.model.terms.LoadTermsResponse | ||
import com.puzzle.network.model.terms.TermResponse | ||
import com.puzzle.network.source.TermDataSource | ||
import io.mockk.Runs | ||
import io.mockk.coEvery | ||
import io.mockk.coVerify | ||
import io.mockk.just | ||
import io.mockk.mockk | ||
import kotlinx.coroutines.test.runTest | ||
import org.junit.jupiter.api.Assertions.assertTrue | ||
import org.junit.jupiter.api.BeforeEach | ||
import org.junit.jupiter.api.Test | ||
|
||
class TermsRepositoryImplTest { | ||
|
||
private lateinit var termDataSource: TermDataSource | ||
private lateinit var localTermDataSource: LocalTermDataSource | ||
private lateinit var termsRepository: TermsRepositoryImpl | ||
|
||
@BeforeEach | ||
fun setUp() { | ||
termDataSource = mockk() | ||
localTermDataSource = mockk() | ||
termsRepository = TermsRepositoryImpl(termDataSource, localTermDataSource) | ||
} | ||
|
||
@Test | ||
fun `약관을 새로 갱신할 경우 id값이 올바르게 내려오지 않은 약관은 무시한다`() = runTest { | ||
// given | ||
val invalidTerm = TermResponse( | ||
termId = UNKNOWN_INT, | ||
title = "Invalid", | ||
content = "Invalid Content", | ||
required = false, | ||
startDate = "2024-06-01T00:00:00", | ||
) | ||
val validTerm = TermResponse( | ||
termId = 1, | ||
title = "Valid", | ||
content = "Valid Content", | ||
required = true, | ||
startDate = "2024-06-01T00:00:00", | ||
) | ||
|
||
coEvery { termDataSource.loadTerms() } returns | ||
Result.success(LoadTermsResponse(listOf(invalidTerm, validTerm))) | ||
coEvery { localTermDataSource.clearAndInsertTerms(any()) } just Runs | ||
|
||
// when | ||
val result = termsRepository.loadTerms() | ||
|
||
// then | ||
assertTrue(result.isSuccess) | ||
coVerify(exactly = 1) { | ||
localTermDataSource.clearAndInsertTerms( | ||
match { | ||
it.size == 1 && it.first().id == validTerm.termId | ||
} | ||
) | ||
} | ||
} | ||
|
||
@Test | ||
fun `갱신한 데이터는 로컬 데이터베이스에 저장한다`() = runTest { | ||
// given | ||
val validTerms = listOf( | ||
TermResponse( | ||
termId = 1, | ||
title = "Valid1", | ||
content = "Content1", | ||
required = true, | ||
startDate = "2024-06-01T00:00:00" | ||
), | ||
TermResponse( | ||
termId = 2, | ||
title = "Valid2", | ||
content = "Content2", | ||
required = false, | ||
startDate = "2024-06-01T00:00:00" | ||
) | ||
) | ||
|
||
coEvery { termDataSource.loadTerms() } returns Result.success(LoadTermsResponse(validTerms)) | ||
coEvery { localTermDataSource.clearAndInsertTerms(any()) } just Runs | ||
|
||
// when | ||
termsRepository.loadTerms() | ||
|
||
// then | ||
coVerify(exactly = 1) { | ||
localTermDataSource.clearAndInsertTerms( | ||
match { | ||
it.size == validTerms.size && it.all { entity -> | ||
validTerms.any { term -> | ||
term.termId == entity.id && term.title == entity.title | ||
} | ||
} | ||
} | ||
) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 허허 꼼꼼한 테스트 작성 👍 👍 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
plugins { | ||
id("piece.android.library") | ||
id("piece.android.hilt") | ||
} | ||
|
||
android { | ||
namespace = "com.puzzle.database" | ||
} | ||
|
||
dependencies { | ||
implementation(projects.core.domain) | ||
implementation(projects.core.common) | ||
|
||
implementation(libs.androidx.room.ktx) | ||
ksp(libs.androidx.room.compiler) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Add project specific ProGuard rules here. | ||
# You can control the set of applied configuration files using the | ||
# proguardFiles setting in build.gradle. | ||
# | ||
# For more details, see | ||
# http://developer.android.com/guide/developing/tools/proguard.html | ||
|
||
# If your project uses WebView with JS, uncomment the following | ||
# and specify the fully qualified class name to the JavaScript interface | ||
# class: | ||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||
# public *; | ||
#} | ||
|
||
# Uncomment this to preserve the line number information for | ||
# debugging stack traces. | ||
#-keepattributes SourceFile,LineNumberTable | ||
|
||
# If you keep the line number information, uncomment this to | ||
# hide the original source file name. | ||
#-renamesourcefileattribute SourceFile |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일단 DefaultValue로 LocalDateTime.MIN을 반환하기로 하였는데,
에러를 바로 던져버릴 지 고민이에요...!