-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
create a dirty favorite tools sync task
- Loading branch information
Showing
5 changed files
with
315 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
library/sync/src/main/kotlin/org/cru/godtools/sync/task/UserFavoriteToolsSyncTasks.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package org.cru.godtools.sync.task | ||
|
||
import javax.inject.Inject | ||
import javax.inject.Singleton | ||
import kotlinx.coroutines.coroutineScope | ||
import kotlinx.coroutines.launch | ||
import kotlinx.coroutines.sync.Mutex | ||
import kotlinx.coroutines.sync.withLock | ||
import org.ccci.gto.android.common.jsonapi.JsonApiConverter | ||
import org.ccci.gto.android.common.jsonapi.retrofit2.JsonApiParams | ||
import org.ccci.gto.android.common.jsonapi.retrofit2.model.JsonApiRetrofitObject | ||
import org.cru.godtools.account.GodToolsAccountManager | ||
import org.cru.godtools.api.UserApi | ||
import org.cru.godtools.api.UserFavoriteToolsApi | ||
import org.cru.godtools.db.repository.ToolsRepository | ||
import org.cru.godtools.db.repository.UserRepository | ||
import org.cru.godtools.model.Tool | ||
import org.cru.godtools.model.User | ||
import org.cru.godtools.sync.repository.SyncRepository | ||
|
||
@Singleton | ||
internal class UserFavoriteToolsSyncTasks @Inject constructor( | ||
private val accountManager: GodToolsAccountManager, | ||
private val favoritesApi: UserFavoriteToolsApi, | ||
private val syncRepository: SyncRepository, | ||
private val toolsRepository: ToolsRepository, | ||
private val userApi: UserApi, | ||
private val userRepository: UserRepository, | ||
) : BaseSyncTasks() { | ||
private val favoritesUpdateMutex = Mutex() | ||
|
||
suspend fun syncDirtyFavoriteTools(): Boolean = favoritesUpdateMutex.withLock { | ||
coroutineScope { | ||
if (!accountManager.isAuthenticated()) return@coroutineScope true | ||
val userId = accountManager.userId().orEmpty() | ||
|
||
val user = userRepository.findUser(userId)?.takeIf { it.isInitialFavoriteToolsSynced } | ||
?: userApi.getUser().takeIf { it.isSuccessful } | ||
?.body()?.dataSingle | ||
?.also { syncRepository.storeUser(it) } | ||
?: return@coroutineScope false | ||
|
||
val favoritesToAdd = toolsRepository.getResources() | ||
.filter { | ||
(it.isFieldChanged(Tool.ATTR_IS_FAVORITE) || !user.isInitialFavoriteToolsSynced) && it.isFavorite | ||
} | ||
|
||
val params = JsonApiParams().fields(Tool.JSONAPI_TYPE, *Tool.JSONAPI_FIELDS) | ||
if (favoritesToAdd.isNotEmpty()) { | ||
favoritesApi.addFavoriteTools(params, favoritesToAdd).takeIf { it.isSuccessful } | ||
?.body()?.data | ||
?.also { syncRepository.storeFavoriteTools(it) } | ||
?: return@coroutineScope false | ||
|
||
if (!user.isInitialFavoriteToolsSynced) { | ||
launch { | ||
val update = JsonApiRetrofitObject.single(User(userId, isInitialFavoriteToolsSynced = true)) | ||
.apply { | ||
options = JsonApiConverter.Options.Builder() | ||
.fields(User.JSONAPI_TYPE, User.JSON_INITIAL_FAVORITE_TOOLS_SYNCED) | ||
.build() | ||
} | ||
|
||
userApi.updateUser(update).takeIf { it.isSuccessful } | ||
?.body()?.dataSingle | ||
?.also { syncRepository.storeUser(it) } | ||
?: return@launch | ||
} | ||
} | ||
} | ||
|
||
val favoritesToRemove = toolsRepository.getResources() | ||
.filter { it.isFieldChanged(Tool.ATTR_IS_FAVORITE) && !it.isFavorite } | ||
|
||
if (favoritesToRemove.isNotEmpty()) { | ||
favoritesApi.removeFavoriteTools(params, favoritesToRemove).takeIf { it.isSuccessful } | ||
?.body()?.data | ||
?.also { syncRepository.storeFavoriteTools(it) } | ||
?: return@coroutineScope false | ||
} | ||
|
||
true | ||
} | ||
} | ||
} |
218 changes: 218 additions & 0 deletions
218
library/sync/src/test/kotlin/org/cru/godtools/sync/task/UserFavoriteToolsSyncTasksTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
package org.cru.godtools.sync.task | ||
|
||
import io.mockk.Called | ||
import io.mockk.Runs | ||
import io.mockk.coEvery | ||
import io.mockk.coExcludeRecords | ||
import io.mockk.coVerifySequence | ||
import io.mockk.just | ||
import io.mockk.mockk | ||
import java.util.UUID | ||
import kotlin.test.Test | ||
import kotlin.test.assertFalse | ||
import kotlin.test.assertTrue | ||
import kotlinx.coroutines.test.runTest | ||
import org.ccci.gto.android.common.jsonapi.model.JsonApiObject | ||
import org.cru.godtools.account.GodToolsAccountManager | ||
import org.cru.godtools.api.UserApi | ||
import org.cru.godtools.api.UserFavoriteToolsApi | ||
import org.cru.godtools.db.repository.ToolsRepository | ||
import org.cru.godtools.db.repository.UserRepository | ||
import org.cru.godtools.model.Tool | ||
import org.cru.godtools.model.User | ||
import org.cru.godtools.model.trackChanges | ||
import org.cru.godtools.sync.repository.SyncRepository | ||
import retrofit2.Response | ||
|
||
class UserFavoriteToolsSyncTasksTest { | ||
private val userId = UUID.randomUUID().toString() | ||
|
||
private val accountManager: GodToolsAccountManager = mockk { | ||
coEvery { isAuthenticated() } returns true | ||
coEvery { userId() } returns userId | ||
} | ||
private val favoritesApi: UserFavoriteToolsApi = mockk { | ||
coEvery { addFavoriteTools(any(), any()) } returns Response.success(JsonApiObject.of()) | ||
coEvery { removeFavoriteTools(any(), any()) } returns Response.success(JsonApiObject.of()) | ||
} | ||
private val syncRepository: SyncRepository = mockk { | ||
coEvery { storeUser(any(), any()) } just Runs | ||
coEvery { storeFavoriteTools(any(), any()) } just Runs | ||
} | ||
private val toolsRepository: ToolsRepository = mockk { | ||
coEvery { getResources() } returns emptyList() | ||
} | ||
private val userApi: UserApi = mockk { | ||
coEvery { getUser(any()) } | ||
.returns(Response.success(JsonApiObject.single(User(userId, isInitialFavoriteToolsSynced = true)))) | ||
coEvery { updateUser(any()) } returns Response.success(JsonApiObject.single(User(userId))) | ||
} | ||
private val userRepository: UserRepository = mockk { | ||
coEvery { findUser(userId) } returns User(userId, isInitialFavoriteToolsSynced = true) | ||
} | ||
|
||
private val tasks = UserFavoriteToolsSyncTasks( | ||
accountManager = accountManager, | ||
favoritesApi = favoritesApi, | ||
syncRepository = syncRepository, | ||
toolsRepository = toolsRepository, | ||
userApi = userApi, | ||
userRepository = userRepository, | ||
) | ||
|
||
// region syncDirtyFavoriteTools() | ||
@Test | ||
fun `syncDirtyFavoriteTools() - add new favorites`() = runTest { | ||
val tools = listOf( | ||
Tool("1") { | ||
id = 1 | ||
isFavorite = true | ||
}, | ||
Tool("2") { | ||
id = 2 | ||
trackChanges { isFavorite = true } | ||
}, | ||
Tool("3") { | ||
id = 3 | ||
isFavorite = false | ||
}, | ||
) | ||
val responseTool = Tool("resp") | ||
|
||
coEvery { toolsRepository.getResources() } returns tools | ||
coEvery { favoritesApi.addFavoriteTools(any(), any()) } returns Response.success(JsonApiObject.of(responseTool)) | ||
|
||
assertTrue(tasks.syncDirtyFavoriteTools()) | ||
coVerifySequence { | ||
favoritesApi.addFavoriteTools(any(), listOf(tools[1])) | ||
syncRepository.storeFavoriteTools(listOf(responseTool), any()) | ||
|
||
userApi wasNot Called | ||
} | ||
} | ||
|
||
@Test | ||
fun `syncDirtyFavoriteTools() - initial favorites`() = runTest { | ||
val user = User(userId, isInitialFavoriteToolsSynced = false) | ||
val tools = listOf( | ||
Tool("1") { | ||
id = 1 | ||
isFavorite = true | ||
}, | ||
Tool("2") { | ||
id = 2 | ||
trackChanges { isFavorite = true } | ||
}, | ||
Tool("3") { | ||
id = 3 | ||
isFavorite = false | ||
}, | ||
) | ||
val responseTool = Tool("resp") | ||
|
||
coEvery { userRepository.findUser(userId) } returns null | ||
coEvery { userApi.getUser(any()) } returns Response.success(JsonApiObject.single(user)) | ||
coEvery { toolsRepository.getResources() } returns tools | ||
coEvery { favoritesApi.addFavoriteTools(any(), any()) } returns Response.success(JsonApiObject.of(responseTool)) | ||
coExcludeRecords { | ||
userApi.getUser(any()) | ||
syncRepository.storeUser(any(), any()) | ||
} | ||
|
||
assertTrue(tasks.syncDirtyFavoriteTools()) | ||
coVerifySequence { | ||
favoritesApi.addFavoriteTools(any(), listOf(tools[0], tools[1])) | ||
syncRepository.storeFavoriteTools(listOf(responseTool), any()) | ||
userApi.updateUser(match { it.dataSingle == User(userId, isInitialFavoriteToolsSynced = true) }) | ||
} | ||
} | ||
|
||
@Test | ||
fun `syncDirtyFavoriteTools() - remove old favorites`() = runTest { | ||
val tools = listOf( | ||
Tool("1") { | ||
id = 1 | ||
isFavorite = true | ||
}, | ||
Tool("2") { | ||
id = 2 | ||
isFavorite = false | ||
}, | ||
Tool("3") { | ||
id = 3 | ||
isFavorite = true | ||
trackChanges { isFavorite = false } | ||
}, | ||
) | ||
val responseTool = Tool("resp") | ||
|
||
coEvery { toolsRepository.getResources() } returns tools | ||
coEvery { favoritesApi.removeFavoriteTools(any(), any()) } | ||
.returns(Response.success(JsonApiObject.of(responseTool))) | ||
|
||
assertTrue(tasks.syncDirtyFavoriteTools()) | ||
coVerifySequence { | ||
favoritesApi.removeFavoriteTools(any(), listOf(tools[2])) | ||
syncRepository.storeFavoriteTools(listOf(responseTool), any()) | ||
|
||
userApi wasNot Called | ||
} | ||
} | ||
|
||
@Test | ||
fun `syncDirtyFavoriteTools() - not authenticated`() = runTest { | ||
coEvery { accountManager.isAuthenticated() } returns false | ||
|
||
assertTrue(tasks.syncDirtyFavoriteTools()) | ||
coVerifySequence { | ||
accountManager.isAuthenticated() | ||
|
||
userRepository wasNot Called | ||
userApi wasNot Called | ||
syncRepository wasNot Called | ||
toolsRepository wasNot Called | ||
favoritesApi wasNot Called | ||
} | ||
} | ||
|
||
@Test | ||
fun `syncDirtyFavoriteTools() - user not found`() = runTest { | ||
coEvery { userRepository.findUser(userId) } returns null | ||
coEvery { userApi.getUser(any()) } returns Response.success(JsonApiObject.of()) | ||
|
||
assertFalse(tasks.syncDirtyFavoriteTools()) | ||
coVerifySequence { | ||
userRepository.findUser(userId) | ||
userApi.getUser(any()) | ||
|
||
syncRepository wasNot Called | ||
toolsRepository wasNot Called | ||
favoritesApi wasNot Called | ||
} | ||
} | ||
|
||
@Test | ||
fun `syncDirtyFavoriteTools() - user resolved from API - user not found`() = runTest { | ||
coEvery { userRepository.findUser(userId) } returns null | ||
|
||
assertTrue(tasks.syncDirtyFavoriteTools()) | ||
coVerifySequence { | ||
userRepository.findUser(userId) | ||
userApi.getUser(any()) | ||
syncRepository.storeUser(any(), any()) | ||
} | ||
} | ||
|
||
@Test | ||
fun `syncDirtyFavoriteTools() - user resolved from API - user hasn't synced initial favorites`() = runTest { | ||
coEvery { userRepository.findUser(userId) } returns User(userId, isInitialFavoriteToolsSynced = false) | ||
|
||
assertTrue(tasks.syncDirtyFavoriteTools()) | ||
coVerifySequence { | ||
userRepository.findUser(userId) | ||
userApi.getUser(any()) | ||
syncRepository.storeUser(any(), any()) | ||
} | ||
} | ||
// endregion syncDirtyFavoriteTools() | ||
} |