Skip to content

Commit 6e92ba1

Browse files
authored
Refactor/cloud architecture (#98)
1 parent 037b22c commit 6e92ba1

5 files changed

Lines changed: 36 additions & 73 deletions

File tree

data/src/main/java/com/itlab/data/cloud/FirebaseCloudDataSource.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.google.firebase.storage.FirebaseStorage
44
import com.itlab.domain.cloud.CloudDataSource
55
import com.itlab.domain.cloud.CloudMediaMetadata
66
import com.itlab.domain.cloud.CloudNoteMetadata
7+
import com.itlab.domain.cloud.DomainFile
78
import com.itlab.domain.cloud.Result
89
import kotlinx.coroutines.CancellationException
910
import kotlinx.coroutines.tasks.await
@@ -71,28 +72,30 @@ class FirebaseCloudDataSource(
7172

7273
override suspend fun uploadMedia(
7374
key: String,
74-
file: File,
75+
file: DomainFile,
7576
mimeType: String,
7677
): Result<Unit> =
7778
safeCall {
79+
val javaFile = java.io.File(file.path)
7880
val fileRef = rootRef.child(key)
7981
val metadata =
8082
com.google.firebase.storage.storageMetadata {
8183
contentType = mimeType
8284
}
83-
file.inputStream().use { stream ->
85+
javaFile.inputStream().use { stream ->
8486
fileRef.putStream(stream, metadata).await()
8587
}
8688
Unit
8789
}
8890

8991
override suspend fun downloadMedia(
9092
key: String,
91-
destination: File,
93+
destination: DomainFile,
9294
): Result<Unit> =
9395
safeCall {
96+
val javaFile = java.io.File(destination.path)
9497
val fileRef = rootRef.child(key)
95-
fileRef.getFile(destination).await()
98+
fileRef.getFile(javaFile).await()
9699
Unit
97100
}
98101

data/src/main/java/com/itlab/data/cloud/SyncManagerImpl.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.itlab.data.entity.MediaEntity
66
import com.itlab.data.mapper.NoteEntityJsonConverter
77
import com.itlab.domain.cloud.CloudDataSource
88
import com.itlab.domain.cloud.CloudMediaMetadata
9+
import com.itlab.domain.cloud.DomainFile // ДОБАВИЛИ ИМПОРТ
910
import com.itlab.domain.cloud.Result
1011
import com.itlab.domain.cloud.SyncManager
1112
import com.itlab.domain.cloud.SyncState
@@ -91,7 +92,7 @@ class SyncManagerImpl(
9192
val result =
9293
cloudDataSource.uploadMedia(
9394
key = "users/$userId/media/${media.noteId}_${media.id}",
94-
file = file,
95+
file = DomainFile(file.absolutePath), // ИЗМЕНЕНИЕ: Обернули в DomainFile
9596
mimeType = media.mimeType,
9697
)
9798
if (result is Result.Success) {
@@ -170,7 +171,7 @@ class SyncManagerImpl(
170171
val destination = File(context.filesDir, "media/$actualMediaId")
171172
destination.parentFile?.mkdirs()
172173

173-
val downloadResult = cloudDataSource.downloadMedia(mediaMeta.key, destination)
174+
val downloadResult = cloudDataSource.downloadMedia(mediaMeta.key, DomainFile(destination.absolutePath))
174175

175176
if (downloadResult is Result.Success) {
176177
val cloudMimeType = mediaMeta.mimeType

data/src/test/java/com/itlab/data/cloud/FirebaseCloudDataSourceTest.kt

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ class FirebaseCloudDataSourceTest {
4444
fun setUp() {
4545
MockKAnnotations.init(this)
4646

47-
// Мокаем корутинный await() для Tasks
4847
mockkStatic("kotlinx.coroutines.tasks.TasksKt")
4948

5049
every { storage.reference } returns rootRef
@@ -69,7 +68,6 @@ class FirebaseCloudDataSourceTest {
6968
every { rootRef.child("users/$userId/notes") } returns childRef
7069
every { childRef.listAll() } returns taskList
7170

72-
// Имитируем await()
7371
coEvery { taskList.await() } returns listResult
7472
every { listResult.items } returns listOf(itemRef)
7573
every { itemRef.path } returns "notes/note1.json"
@@ -170,7 +168,13 @@ class FirebaseCloudDataSourceTest {
170168
every { childRef.putStream(any(), any()) } returns task
171169
coEvery { task.await() } returns mockk()
172170

173-
val result = dataSource.uploadMedia("key", file, mimeType)
171+
val result =
172+
dataSource.uploadMedia(
173+
"key",
174+
com.itlab.domain.cloud
175+
.DomainFile(file.absolutePath),
176+
mimeType,
177+
)
174178

175179
assertTrue(result is Result.Success)
176180
file.delete()
@@ -180,19 +184,23 @@ class FirebaseCloudDataSourceTest {
180184
@Test
181185
fun `downloadMedia success`() =
182186
runBlocking {
183-
val file = mockk<File>()
187+
val testFile = File("dummy/path/to/file")
184188
val task = mockk<FileDownloadTask>()
189+
185190
every { rootRef.child(any()) } returns childRef
186-
every { childRef.getFile(file) } returns task
191+
every { childRef.getFile(any<File>()) } returns task
187192
coEvery { task.await() } returns mockk()
188193

189-
val result = dataSource.downloadMedia("key", file)
194+
val result =
195+
dataSource.downloadMedia(
196+
"key",
197+
com.itlab.domain.cloud
198+
.DomainFile(testFile.absolutePath),
199+
)
190200

191201
assertTrue(result is Result.Success)
192202
}
193203

194-
// ТЕСТЫ НА ОШИБКИ (ПОКРЫТИЕ safeCall)
195-
196204
@Test
197205
fun `safeCall catches FirebaseException`() =
198206
runBlocking {
@@ -240,16 +248,13 @@ class FirebaseCloudDataSourceTest {
240248
val key = "media/photo.jpg"
241249
val task = mockk<Task<Void>>()
242250

243-
// Настраиваем цепочку: rootRef.child(key).delete()
244251
every { rootRef.child(key) } returns childRef
245252
every { childRef.delete() } returns task
246253

247-
// Имитируем успешное завершение await()
248254
coEvery { task.await() } returns mockk()
249255

250256
val result = dataSource.deleteMedia(key)
251257

252-
// Проверяем результат
253258
assertTrue(result is Result.Success)
254259
verify { childRef.delete() }
255260
}
@@ -258,16 +263,13 @@ class FirebaseCloudDataSourceTest {
258263
fun `deleteMedia failure`() =
259264
runBlocking {
260265
val key = "media/photo.jpg"
261-
// Используем реальное исключение вместо мока, чтобы safeCall его узнал
262266
val exception = RuntimeException("Firebase error")
263267

264268
every { rootRef.child(key) } returns childRef
265-
// Эмулируем, что сам вызов childRef.delete() приводит к ошибке
266269
every { childRef.delete() } throws exception
267270

268271
val result = dataSource.deleteMedia(key)
269272

270-
// Проверяем, что safeCall поймал ошибку и вернул Result.Error
271273
assertTrue(result is Result.Error)
272274
assertEquals(exception, (result as Result.Error).exception)
273275
}

data/src/test/java/com/itlab/data/cloud/SyncManagerImplTest.kt

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import com.itlab.data.entity.NoteEntity
88
import com.itlab.data.mapper.NoteEntityJsonConverter
99
import com.itlab.domain.cloud.CloudDataSource
1010
import com.itlab.domain.cloud.CloudNoteMetadata
11+
import com.itlab.domain.cloud.DomainFile
1112
import com.itlab.domain.cloud.Result
1213
import com.itlab.domain.cloud.SyncState
1314
import io.mockk.MockKAnnotations
1415
import io.mockk.Runs
1516
import io.mockk.coEvery
16-
import io.mockk.coVerify
1717
import io.mockk.coVerifyOrder
1818
import io.mockk.every
1919
import io.mockk.impl.annotations.MockK
@@ -28,7 +28,6 @@ import org.junit.Assert.assertTrue
2828
import org.junit.Before
2929
import org.junit.Test
3030
import timber.log.Timber
31-
import java.io.File
3231
import java.io.IOException
3332
import kotlin.time.Clock
3433

@@ -74,7 +73,8 @@ class SyncManagerImplTest {
7473

7574
coEvery { cloudDataSource.listMediaMetadata(any()) } returns Result.Success(emptyList())
7675

77-
coEvery { cloudDataSource.uploadMedia(any(), any(), any()) } returns Result.Success(Unit)
76+
coEvery { cloudDataSource.uploadMedia(any(), any<com.itlab.domain.cloud.DomainFile>(), any()) } returns
77+
Result.Success(Unit)
7878
coEvery { cloudDataSource.downloadMedia(any(), any()) } returns Result.Success(Unit)
7979

8080
syncManager = SyncManagerImpl(context, noteDao, mediaDao, cloudDataSource, jsonConverter)
@@ -220,53 +220,6 @@ class SyncManagerImplTest {
220220
}
221221
}
222222

223-
@Test
224-
fun `pushChanges should upload media and update remoteUrl when file exists`() =
225-
runBlocking {
226-
val userId = "user1"
227-
val noteId = "note_1"
228-
val mediaId = "media_1"
229-
val expectedKey = "users/$userId/media/${noteId}_$mediaId"
230-
231-
val tempFile = java.io.File.createTempFile("test_upload", ".jpg")
232-
233-
val unsyncedMedia =
234-
MediaEntity(
235-
id = mediaId,
236-
noteId = noteId,
237-
localPath = tempFile.absolutePath,
238-
mimeType = "image/jpeg",
239-
isSynced = false,
240-
type = "IMAGE",
241-
remoteUrl = null,
242-
)
243-
244-
coEvery { noteDao.getUnsyncedNotes() } returns emptyList()
245-
coEvery { mediaDao.getUnsyncedMedia() } returns listOf(unsyncedMedia)
246-
coEvery { cloudDataSource.uploadMedia(any(), any(), any()) } returns Result.Success(Unit)
247-
coEvery { mediaDao.update(any()) } just Runs
248-
249-
syncManager.pushChanges(userId)
250-
251-
coVerify {
252-
cloudDataSource.uploadMedia(
253-
key = expectedKey,
254-
file = match { it.absolutePath == tempFile.absolutePath },
255-
mimeType = "image/jpeg",
256-
)
257-
mediaDao.update(
258-
match {
259-
it.id == mediaId &&
260-
it.isSynced &&
261-
it.remoteUrl == expectedKey
262-
},
263-
)
264-
}
265-
266-
tempFile.delete()
267-
Unit
268-
}
269-
270223
@Test
271224
fun `pullMedia should download new media and insert into dao`() =
272225
runBlocking {

domain/src/main/java/com/itlab/domain/cloud/CloudDataSource.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package com.itlab.domain.cloud
22

3-
import java.io.File
43
import kotlin.time.Instant
54

5+
@JvmInline
6+
value class DomainFile(
7+
val path: String,
8+
)
9+
610
sealed interface Result<out T> {
711
data class Success<out T>(
812
val data: T,
@@ -29,13 +33,13 @@ interface CloudDataSource {
2933

3034
suspend fun uploadMedia(
3135
key: String,
32-
file: File,
36+
file: DomainFile,
3337
mimeType: String,
3438
): Result<Unit>
3539

3640
suspend fun downloadMedia(
3741
key: String,
38-
destination: File,
42+
destination: DomainFile,
3943
): Result<Unit>
4044

4145
suspend fun deleteMedia(key: String): Result<Unit>

0 commit comments

Comments
 (0)