@@ -14,6 +14,7 @@ import com.threegap.bitnagil.domain.routine.model.RoutineSchedule
1414import com.threegap.bitnagil.domain.routine.repository.RoutineRepository
1515import kotlinx.coroutines.ExperimentalCoroutinesApi
1616import kotlinx.coroutines.flow.first
17+ import kotlinx.coroutines.launch
1718import kotlinx.coroutines.test.StandardTestDispatcher
1819import kotlinx.coroutines.test.advanceTimeBy
1920import kotlinx.coroutines.test.runCurrent
@@ -125,7 +126,7 @@ class RoutineRepositoryImplTest {
125126 repository.applyRoutineToggle(dateKey2, routineId2, RoutineCompletionInfo (routineId2, routineCompleteYn = true , subRoutineCompleteYn = emptyList()))
126127
127128 advanceTimeBy(501L )
128- assertEquals(2 , remoteDataSource.syncCount.get())
129+ assertEquals(1 , remoteDataSource.syncCount.get())
129130 }
130131
131132 @Test
@@ -146,7 +147,7 @@ class RoutineRepositoryImplTest {
146147 @Test
147148 fun `sync 실패 시 서버 데이터로 로컬 캐시가 rollback되어야 한다` () = runTest {
148149 repository = createRepository(testScheduler)
149- runCurrent() // repositoryScope의 syncTrigger collector 시작
150+ runCurrent()
150151 remoteDataSource.scheduleResponse = Result .success(RoutineScheduleResponse (emptyMap()))
151152 remoteDataSource.syncResult = Result .failure(Exception (" 네트워크 오류" ))
152153 val (dateKey, routineId) = setupCacheWithRoutine(isCompleted = false )
@@ -156,10 +157,11 @@ class RoutineRepositoryImplTest {
156157 routineId = routineId,
157158 completionInfo = RoutineCompletionInfo (routineId, routineCompleteYn = true , subRoutineCompleteYn = emptyList()),
158159 )
159- advanceTimeBy(501L )
160160
161- // sync 실패 후 fetchAndSave 호출 → 서버 값(emptyMap)으로 덮어씀
162- assertEquals(1 , remoteDataSource.fetchCount.get())
161+ advanceTimeBy(1200L ) // debounce(500) + retry delay(200 + 400) + 여유
162+
163+ assertEquals(3 , remoteDataSource.syncCount.get()) // 1회 시도 + 2회 재시도
164+ assertEquals(1 , remoteDataSource.fetchCount.get()) // 롤백 fetch 1회
163165 assertEquals(RoutineSchedule (emptyMap()), localDataSource.routineSchedule.value)
164166 }
165167
@@ -187,6 +189,33 @@ class RoutineRepositoryImplTest {
187189 assertEquals(0 , remoteDataSource.fetchCount.get())
188190 }
189191
192+ @Test
193+ fun `sync 1차 실패 후 재시도 성공 시 syncError가 emit되지 않아야 한다` () = runTest {
194+ repository = createRepository(testScheduler)
195+ runCurrent()
196+ remoteDataSource.syncFailCount = 1 // 1번만 실패, 이후 성공
197+ val (dateKey, routineId) = setupCacheWithRoutine(isCompleted = false )
198+
199+ var syncErrorEmitted = false
200+ val job = launch {
201+ repository.syncError.collect { syncErrorEmitted = true }
202+ }
203+
204+ repository.applyRoutineToggle(
205+ dateKey = dateKey,
206+ routineId = routineId,
207+ completionInfo = RoutineCompletionInfo (routineId, routineCompleteYn = true , subRoutineCompleteYn = emptyList()),
208+ )
209+
210+ advanceTimeBy(1200L ) // debounce + 재시도 여유 시간
211+
212+ assertEquals(2 , remoteDataSource.syncCount.get()) // 1차 실패 + 2차 성공
213+ assertEquals(false , syncErrorEmitted) // 에러 다이얼로그 없음
214+ assertEquals(0 , remoteDataSource.fetchCount.get()) // 롤백 fetch 없음
215+
216+ job.cancel()
217+ }
218+
190219 // --- Helpers ---
191220
192221 private fun setupCacheWithRoutine (isCompleted : Boolean ): Pair <String , String > =
@@ -225,6 +254,7 @@ class RoutineRepositoryImplTest {
225254 var scheduleResponse: Result <RoutineScheduleResponse > = Result .success(RoutineScheduleResponse (emptyMap()))
226255 var syncResult: Result <Unit > = Result .success(Unit )
227256 var deleteResult: Result <Unit > = Result .success(Unit )
257+ var syncFailCount: Int = 0
228258 val fetchCount = AtomicInteger (0 )
229259 val syncCount = AtomicInteger (0 )
230260
@@ -234,8 +264,12 @@ class RoutineRepositoryImplTest {
234264 }
235265
236266 override suspend fun syncRoutineCompletion (routineCompletionRequest : RoutineCompletionRequest ): Result <Unit > {
237- syncCount.incrementAndGet()
238- return syncResult
267+ val attempt = syncCount.incrementAndGet()
268+ return if (attempt <= syncFailCount) {
269+ Result .failure(Exception (" 네트워크 오류" ))
270+ } else {
271+ syncResult
272+ }
239273 }
240274
241275 override suspend fun getRoutine (routineId : String ): Result <com.threegap.bitnagil.data.routine.model.response.RoutineResponse > =
0 commit comments