Skip to content

Commit d2c4881

Browse files
committed
[TEST/#256] 운세 생성 및 상태 관리 로직 테스트 코드 추가
1 parent c87bad1 commit d2c4881

File tree

1 file changed

+220
-0
lines changed

1 file changed

+220
-0
lines changed
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
package com.yapp.datastore
2+
3+
import androidx.datastore.core.DataStore
4+
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
5+
import androidx.datastore.preferences.core.Preferences
6+
import androidx.datastore.preferences.core.booleanPreferencesKey
7+
import androidx.datastore.preferences.core.edit
8+
import kotlinx.coroutines.flow.first
9+
import kotlinx.coroutines.test.runTest
10+
import org.junit.Assert.assertEquals
11+
import org.junit.Rule
12+
import org.junit.Test
13+
import org.junit.rules.TemporaryFolder
14+
import java.time.Clock
15+
import java.time.Instant
16+
import java.time.LocalDate
17+
import java.time.ZoneOffset
18+
import java.util.UUID
19+
20+
class FortunePreferencesTest {
21+
22+
@get:Rule
23+
val temporaryFolder = TemporaryFolder()
24+
25+
private val fixedZoneOffsetUtc = ZoneOffset.UTC
26+
27+
private val baseInstantAtT0: Instant = Instant.parse("2025-09-17T00:00:00Z")
28+
private val baseInstantAtT0Plus2Seconds: Instant = Instant.parse("2025-09-17T00:00:02Z")
29+
30+
private val fixedClockAtT0: Clock = Clock.fixed(baseInstantAtT0, fixedZoneOffsetUtc)
31+
private val fixedClockAtT0Plus2Seconds: Clock =
32+
Clock.fixed(baseInstantAtT0Plus2Seconds, fixedZoneOffsetUtc)
33+
34+
private val referenceInstantForAnyDay: Instant = Instant.parse("2025-09-17T00:00:00Z")
35+
private val fixedClockForReferenceDay: Clock =
36+
Clock.fixed(referenceInstantForAnyDay, fixedZoneOffsetUtc)
37+
38+
private fun createNewDataStoreWithFile(fileName: String): DataStore<Preferences> =
39+
PreferenceDataStoreFactory.create {
40+
temporaryFolder.newFile(fileName)
41+
}
42+
43+
private fun createFortunePreferencesWithClock(
44+
dataStore: DataStore<Preferences>,
45+
fixedClock: Clock,
46+
): FortunePreferences = FortunePreferences(dataStore, fixedClock)
47+
48+
@Test
49+
fun `운세_생성_상태_Creating_만료_시_Failure로_교정된다`() = runTest {
50+
// given: t0 시점에서 Creating(lease 1초) 설정
51+
val dataStoreAtT0 = createNewDataStoreWithFile("prefs_expire.preferences_pb")
52+
val preferencesAtT0 =
53+
createFortunePreferencesWithClock(dataStoreAtT0, fixedClockAtT0)
54+
55+
val generatedAttemptId = UUID.randomUUID().toString()
56+
preferencesAtT0.markFortuneCreating(attemptId = generatedAttemptId, lease = 1_000L)
57+
58+
// when: t0 + 2초 경과 후 같은 DataStore를 새로운 Clock으로 읽음
59+
val preferencesAtT0Plus2Seconds =
60+
createFortunePreferencesWithClock(dataStoreAtT0, fixedClockAtT0Plus2Seconds)
61+
62+
// then: Creating → false, Failed → true
63+
val creating = preferencesAtT0Plus2Seconds.isFortuneCreatingFlow.first()
64+
assertEquals(false, creating)
65+
val failed = preferencesAtT0Plus2Seconds.isFortuneFailedFlow.first()
66+
assertEquals(true, failed)
67+
}
68+
69+
@Test
70+
fun `만료_정보_없는_운세_생성_상태_Creating은_즉시_Failure로_교정된다`() = runTest {
71+
// given: 과거 버전 데이터 (Creating=true만 존재)
72+
val dataStoreWithLegacyCreating = createNewDataStoreWithFile("prefs_legacy.preferences_pb")
73+
val keyCreatingOnly = booleanPreferencesKey("fortune_creating")
74+
dataStoreWithLegacyCreating.edit { it[keyCreatingOnly] = true }
75+
76+
val preferencesFromLegacyData =
77+
createFortunePreferencesWithClock(dataStoreWithLegacyCreating, fixedClockForReferenceDay)
78+
79+
// when: Failure로 교정 로직이 표시된 Flow 구독 시작
80+
81+
// then: 즉시 Creating → false, Failed → true
82+
val creating = preferencesFromLegacyData.isFortuneCreatingFlow.first()
83+
assertEquals(false, creating)
84+
val failed = preferencesFromLegacyData.isFortuneFailedFlow.first()
85+
assertEquals(true, failed)
86+
}
87+
88+
@Test
89+
fun `생성_성공_시_attemptId가_일치할_때만_운세_생성_상태가_Creating에서_Success로_전환된다`() = runTest {
90+
// given: 운세 Creating 상태
91+
val dataStore = createNewDataStoreWithFile("prefs_success.preferences_pb")
92+
val preferences = createFortunePreferencesWithClock(dataStore, fixedClockForReferenceDay)
93+
val validAttemptId = "ATTEMPT_VALID"
94+
val invalidAttemptId = "ATTEMPT_INVALID"
95+
val createdFortuneId = 99L
96+
preferences.markFortuneCreating(attemptId = validAttemptId, lease = 60_000L)
97+
98+
// when: 잘못된 attemptId로 생성 성공 처리 시도
99+
preferences.markFortuneCreatedIfAttemptMatches(
100+
attemptId = invalidAttemptId,
101+
fortuneId = createdFortuneId
102+
)
103+
104+
// then: 여전히 Creating 상태 (무시됨)
105+
val stillCreating = preferences.isFortuneCreatingFlow.first()
106+
assertEquals(true, stillCreating)
107+
108+
// when: 올바른 attemptId로 생성 성공 처리 시도
109+
preferences.markFortuneCreatedIfAttemptMatches(
110+
attemptId = validAttemptId,
111+
fortuneId = createdFortuneId
112+
)
113+
114+
// then: Creating → false, Failed → false, fortuneId 및 날짜 설정
115+
val creatingAfterSuccess = preferences.isFortuneCreatingFlow.first()
116+
assertEquals(false, creatingAfterSuccess)
117+
val failedAfterSuccess = preferences.isFortuneFailedFlow.first()
118+
assertEquals(false, failedAfterSuccess)
119+
val savedId = preferences.fortuneIdFlow.first()
120+
assertEquals(createdFortuneId, savedId)
121+
val savedEpoch = preferences.fortuneDateEpochFlow.first()
122+
assertEquals(LocalDate.now(fixedClockForReferenceDay).toEpochDay(), savedEpoch)
123+
}
124+
125+
@Test
126+
fun `운세_생성_실패_시_attemptId가_일치할_때만_Failure로_전환된다`() = runTest {
127+
// given: 운세 Creating 상태
128+
val dataStore = createNewDataStoreWithFile("prefs_fail.preferences_pb")
129+
val preferences = createFortunePreferencesWithClock(dataStore, fixedClockForReferenceDay)
130+
val validAttemptId = "ATTEMPT_VALID"
131+
val invalidAttemptId = "ATTEMPT_INVALID"
132+
preferences.markFortuneCreating(attemptId = validAttemptId, lease = 60_000L)
133+
134+
// when: 잘못된 attemptId로 실패 처리 시도
135+
preferences.markFortuneFailedIfAttemptMatches(invalidAttemptId)
136+
137+
// then: 아직 Creating 상태 (무시됨)
138+
val stillCreating = preferences.isFortuneCreatingFlow.first()
139+
assertEquals(true, stillCreating)
140+
141+
// when: 올바른 attemptId로 실패 처리 시도
142+
preferences.markFortuneFailedIfAttemptMatches(validAttemptId)
143+
144+
// then: Creating → false, Failed → true
145+
val creatingAfterFail = preferences.isFortuneCreatingFlow.first()
146+
assertEquals(false, creatingAfterFail)
147+
val failed = preferences.isFortuneFailedFlow.first()
148+
assertEquals(true, failed)
149+
}
150+
151+
@Test
152+
fun `오늘_운세가_있고_확인한_경우_hasUnseenFortune가_false`() = runTest {
153+
// given: 오늘 운세가 생성되어 있고(미확인)
154+
val dataStore = createNewDataStoreWithFile("prefs_seen.preferences_pb")
155+
val preferences = createFortunePreferencesWithClock(dataStore, fixedClockForReferenceDay)
156+
val attemptId = "ATTEMPT_FOR_SEEN"
157+
val fortuneId = 777L
158+
preferences.markFortuneCreating(attemptId = attemptId, lease = 60_000L)
159+
preferences.markFortuneCreatedIfAttemptMatches(attemptId = attemptId, fortuneId = fortuneId)
160+
161+
// when: 사용자가 오늘 운세를 확인
162+
preferences.markFortuneSeen()
163+
164+
// then: hasUnseenFortune → false
165+
val unseen = preferences.hasUnseenFortuneFlow.first()
166+
assertEquals(false, unseen)
167+
}
168+
169+
@Test
170+
fun `오늘_운세가_있고_아직_확인하지_않은_경우_hasUnseenFortune가_true`() = runTest {
171+
// given: 오늘 운세가 생성되어 있는 상태(미확인)
172+
val dataStore = createNewDataStoreWithFile("prefs_unseen.preferences_pb")
173+
val preferences = createFortunePreferencesWithClock(dataStore, fixedClockForReferenceDay)
174+
val attemptId = "ATTEMPT_FOR_UNSEEN"
175+
val fortuneId = 123L
176+
preferences.markFortuneCreating(attemptId = attemptId, lease = 60_000L)
177+
preferences.markFortuneCreatedIfAttemptMatches(attemptId = attemptId, fortuneId = fortuneId)
178+
179+
// when: hasUnseenFortuneFlow 구독
180+
181+
// then: 오늘 운세 존재 + 아직 읽지 않음 = hasUnseenFortune → true
182+
val unseen = preferences.hasUnseenFortuneFlow.first()
183+
assertEquals(true, unseen)
184+
}
185+
186+
@Test
187+
fun `오늘_운세가_있고_Tooltip을_보여주었다면_shouldShowFortuneToolTip이_false`() = runTest {
188+
// given: 오늘 운세가 생성되어 있는 상태(툴팁 미표시)
189+
val dataStore = createNewDataStoreWithFile("prefs_tooltip_true.preferences_pb")
190+
val preferences = createFortunePreferencesWithClock(dataStore, fixedClockForReferenceDay)
191+
val attemptId = "ATTEMPT_FOR_TOOLTIP_TRUE"
192+
val fortuneId = 888L
193+
preferences.markFortuneCreating(attemptId = attemptId, lease = 60_000L)
194+
preferences.markFortuneCreatedIfAttemptMatches(attemptId = attemptId, fortuneId = fortuneId)
195+
196+
// when: ToolTip을 보여줌
197+
preferences.markFortuneTooltipShown()
198+
199+
// then: shouldShowFortuneToolTip → false
200+
val showTooltip = preferences.shouldShowFortuneToolTipFlow.first()
201+
assertEquals(false, showTooltip)
202+
}
203+
204+
@Test
205+
fun `오늘_운세가_있고_Tooltip을_아직_보여주지_않았다면_shouldShowFortuneToolTip이_true`() = runTest {
206+
// given: 오늘 운세가 생성되어 있는 상태(툴팁 미표시)
207+
val dataStore = createNewDataStoreWithFile("prefs_tooltip.preferences_pb")
208+
val preferences = createFortunePreferencesWithClock(dataStore, fixedClockForReferenceDay)
209+
val attemptId = "ATTEMPT_FOR_TOOLTIP"
210+
val fortuneId = 456L
211+
preferences.markFortuneCreating(attemptId = attemptId, lease = 60_000L)
212+
preferences.markFortuneCreatedIfAttemptMatches(attemptId = attemptId, fortuneId = fortuneId)
213+
214+
// when: shouldShowFortuneToolTipFlow 구독
215+
216+
// then: 오늘 운세 존재 + 툴팁 미표시 = shouldShowFortuneToolTip → true
217+
val showTooltip = preferences.shouldShowFortuneToolTipFlow.first()
218+
assertEquals(true, showTooltip)
219+
}
220+
}

0 commit comments

Comments
 (0)