-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Siddharth Agarwal
committed
Dec 9, 2024
1 parent
3d1b8ac
commit 6b9b13c
Showing
12 changed files
with
328 additions
and
5 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
179 changes: 179 additions & 0 deletions
179
app/src/androidTest/java/org/simple/clinic/sync/PatientAttributeSyncIntegrationTest.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,179 @@ | ||
package org.simple.clinic.sync | ||
|
||
import com.f2prateek.rx.preferences2.Preference | ||
import com.google.common.truth.Truth.assertThat | ||
import org.junit.Before | ||
import org.junit.Rule | ||
import org.junit.Test | ||
import org.junit.rules.RuleChain | ||
import org.simple.clinic.AppDatabase | ||
import org.simple.clinic.TestClinicApp | ||
import org.simple.clinic.main.TypedPreference | ||
import org.simple.clinic.main.TypedPreference.Type.LastPatientAttributePullToken | ||
import org.simple.clinic.patient.SyncStatus | ||
import org.simple.clinic.patientattribute.BMIReading | ||
import org.simple.clinic.patientattribute.PatientAttribute | ||
import org.simple.clinic.patientattribute.PatientAttributeRepository | ||
import org.simple.clinic.patientattribute.sync.PatientAttributeSync | ||
import org.simple.clinic.patientattribute.sync.PatientAttributeSyncApi | ||
import org.simple.clinic.rules.RegisterPatientRule | ||
import org.simple.clinic.rules.SaveDatabaseRule | ||
import org.simple.clinic.rules.ServerAuthenticationRule | ||
import org.simple.clinic.user.UserSession | ||
import org.simple.clinic.util.unsafeLazy | ||
import org.simple.sharedTestCode.TestData | ||
import org.simple.sharedTestCode.util.Rules | ||
import java.util.Optional | ||
import java.util.UUID | ||
import javax.inject.Inject | ||
|
||
class PatientAttributeSyncIntegrationTest { | ||
|
||
@Inject | ||
lateinit var appDatabase: AppDatabase | ||
|
||
@Inject | ||
lateinit var repository: PatientAttributeRepository | ||
|
||
@Inject | ||
@TypedPreference(LastPatientAttributePullToken) | ||
lateinit var lastPullToken: Preference<Optional<String>> | ||
|
||
@Inject | ||
lateinit var syncApi: PatientAttributeSyncApi | ||
|
||
@Inject | ||
lateinit var userSession: UserSession | ||
|
||
@Inject | ||
lateinit var syncInterval: SyncInterval | ||
|
||
private val patientUuid = UUID.fromString("9af4f083-86dd-453f-91e5-9c716e859a9e") | ||
|
||
private val userUuid: UUID by unsafeLazy { userSession.loggedInUserImmediate()!!.uuid } | ||
|
||
@get:Rule | ||
val ruleChain: RuleChain = Rules | ||
.global() | ||
.around(ServerAuthenticationRule()) | ||
.around(RegisterPatientRule(patientUuid)) | ||
.around(SaveDatabaseRule()) | ||
|
||
private lateinit var sync: PatientAttributeSync | ||
|
||
private val batchSize = 3 | ||
|
||
private lateinit var config: SyncConfig | ||
|
||
@Before | ||
fun setUp() { | ||
TestClinicApp.appComponent().inject(this) | ||
|
||
resetLocalData() | ||
|
||
config = SyncConfig( | ||
syncInterval = syncInterval, | ||
pullBatchSize = batchSize, | ||
pushBatchSize = batchSize, | ||
name = "" | ||
) | ||
|
||
sync = PatientAttributeSync( | ||
syncCoordinator = SyncCoordinator(), | ||
repository = repository, | ||
api = syncApi, | ||
lastPullToken = lastPullToken, | ||
config = config | ||
) | ||
} | ||
|
||
private fun resetLocalData() { | ||
clearData() | ||
lastPullToken.delete() | ||
} | ||
|
||
private fun clearData() { | ||
appDatabase.patientAttributeDao().clear() | ||
} | ||
|
||
@Test | ||
fun syncing_records_should_work_as_expected() { | ||
// given | ||
val totalNumberOfRecords = batchSize * 2 + 1 | ||
val records = (1..totalNumberOfRecords).map { | ||
TestData.patientAttribute( | ||
patientUuid = patientUuid, | ||
userUuid = userUuid, | ||
reading = BMIReading( | ||
height = "177", | ||
weight = "68" | ||
), | ||
syncStatus = SyncStatus.PENDING, | ||
) | ||
} | ||
assertThat(records).containsNoDuplicates() | ||
|
||
repository.save(records) | ||
assertThat(repository.pendingSyncRecordCount().blockingFirst()).isEqualTo(totalNumberOfRecords) | ||
|
||
// when | ||
sync.push() | ||
clearData() | ||
sync.pull() | ||
|
||
// then | ||
val expectedPulledRecords = records.map { it.syncCompleted() } | ||
val pulledRecords = repository.recordsWithSyncStatus(SyncStatus.DONE) | ||
|
||
assertThat(pulledRecords).containsAtLeastElementsIn(expectedPulledRecords) | ||
} | ||
|
||
@Test | ||
fun sync_pending_records_should_not_be_overwritten_by_server_records() { | ||
// given | ||
val records = (1..batchSize).map { | ||
TestData.patientAttribute( | ||
patientUuid = patientUuid, | ||
userUuid = userUuid, | ||
reading = BMIReading( | ||
height = "177", | ||
weight = "68" | ||
), | ||
syncStatus = SyncStatus.PENDING, | ||
) | ||
} | ||
assertThat(records).containsNoDuplicates() | ||
|
||
repository.save(records) | ||
sync.push() | ||
assertThat(repository.pendingSyncRecordCount().blockingFirst()).isEqualTo(0) | ||
|
||
val modifiedRecord = records[1].withReading(BMIReading(height = "182", weight = "78")) | ||
repository.save(listOf(modifiedRecord)) | ||
assertThat(repository.pendingSyncRecordCount().blockingFirst()).isEqualTo(1) | ||
|
||
// when | ||
sync.pull() | ||
|
||
// then | ||
val expectedSavedRecords = records | ||
.map { it.syncCompleted() } | ||
.filterNot { it.patientUuid == modifiedRecord.patientUuid } | ||
|
||
val savedRecords = repository.recordsWithSyncStatus(SyncStatus.DONE) | ||
val pendingSyncRecords = repository.recordsWithSyncStatus(SyncStatus.PENDING) | ||
|
||
assertThat(savedRecords).containsAtLeastElementsIn(expectedSavedRecords) | ||
assertThat(pendingSyncRecords).containsExactly(modifiedRecord) | ||
} | ||
|
||
private fun PatientAttribute.syncCompleted(): PatientAttribute = copy(syncStatus = SyncStatus.DONE) | ||
|
||
private fun PatientAttribute.withReading(reading: BMIReading): PatientAttribute { | ||
return copy( | ||
reading = reading.copy(height = reading.height, weight = reading.weight), | ||
syncStatus = SyncStatus.PENDING, | ||
timestamps = timestamps.copy(updatedAt = timestamps.updatedAt.plusMillis(1)) | ||
) | ||
} | ||
} |
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
21 changes: 21 additions & 0 deletions
21
app/src/main/java/org/simple/clinic/patientattribute/PatientAttributeModule.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 |
---|---|---|
@@ -1,13 +1,34 @@ | ||
package org.simple.clinic.patientattribute | ||
|
||
import com.f2prateek.rx.preferences2.Preference | ||
import com.f2prateek.rx.preferences2.RxSharedPreferences | ||
import dagger.Module | ||
import dagger.Provides | ||
import org.simple.clinic.AppDatabase | ||
import org.simple.clinic.main.TypedPreference | ||
import org.simple.clinic.main.TypedPreference.Type.LastPatientAttributePullToken | ||
import org.simple.clinic.patientattribute.sync.PatientAttributeSyncApi | ||
import org.simple.clinic.util.preference.StringPreferenceConverter | ||
import org.simple.clinic.util.preference.getOptional | ||
import retrofit2.Retrofit | ||
import java.util.Optional | ||
import javax.inject.Named | ||
|
||
@Module | ||
class PatientAttributeModule { | ||
@Provides | ||
fun dao(appDatabase: AppDatabase): PatientAttribute.RoomDao { | ||
return appDatabase.patientAttributeDao() | ||
} | ||
|
||
@Provides | ||
fun syncApi(@Named("for_deployment") retrofit: Retrofit): PatientAttributeSyncApi { | ||
return retrofit.create(PatientAttributeSyncApi::class.java) | ||
} | ||
|
||
@Provides | ||
@TypedPreference(LastPatientAttributePullToken) | ||
fun lastPullToken(rxSharedPrefs: RxSharedPreferences): Preference<Optional<String>> { | ||
return rxSharedPrefs.getOptional("last_patient_attribute_pull_token", StringPreferenceConverter()) | ||
} | ||
} |
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
17 changes: 17 additions & 0 deletions
17
app/src/main/java/org/simple/clinic/patientattribute/sync/PatientAttributePullResponse.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,17 @@ | ||
package org.simple.clinic.patientattribute.sync | ||
|
||
import com.squareup.moshi.Json | ||
import com.squareup.moshi.JsonClass | ||
import org.simple.clinic.sync.DataPullResponse | ||
|
||
@JsonClass(generateAdapter = true) | ||
data class PatientAttributePullResponse( | ||
|
||
@Json(name = "patient_attributes") | ||
override val payloads: List<PatientAttributePayload>, | ||
|
||
@Json(name = "process_token") | ||
override val processToken: String | ||
|
||
) : DataPullResponse<PatientAttributePayload> | ||
|
11 changes: 11 additions & 0 deletions
11
app/src/main/java/org/simple/clinic/patientattribute/sync/PatientAttributePushRequest.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,11 @@ | ||
package org.simple.clinic.patientattribute.sync | ||
|
||
import com.squareup.moshi.Json | ||
import com.squareup.moshi.JsonClass | ||
|
||
@JsonClass(generateAdapter = true) | ||
data class PatientAttributePushRequest( | ||
|
||
@Json(name = "patient_attributes") | ||
val histories: List<PatientAttributePayload> | ||
) |
56 changes: 56 additions & 0 deletions
56
app/src/main/java/org/simple/clinic/patientattribute/sync/PatientAttributeSync.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,56 @@ | ||
package org.simple.clinic.patientattribute.sync | ||
|
||
import com.f2prateek.rx.preferences2.Preference | ||
import org.simple.clinic.main.TypedPreference | ||
import org.simple.clinic.main.TypedPreference.Type.LastPatientAttributePullToken | ||
import org.simple.clinic.patientattribute.PatientAttribute | ||
import org.simple.clinic.patientattribute.PatientAttributeRepository | ||
import org.simple.clinic.sync.ModelSync | ||
import org.simple.clinic.sync.SyncConfig | ||
import org.simple.clinic.sync.SyncConfigType | ||
import org.simple.clinic.sync.SyncConfigType.Type.Frequent | ||
import org.simple.clinic.sync.SyncCoordinator | ||
import org.simple.clinic.util.read | ||
import java.util.Optional | ||
import javax.inject.Inject | ||
|
||
class PatientAttributeSync @Inject constructor( | ||
private val syncCoordinator: SyncCoordinator, | ||
private val repository: PatientAttributeRepository, | ||
private val api: PatientAttributeSyncApi, | ||
@TypedPreference(LastPatientAttributePullToken) private val lastPullToken: Preference<Optional<String>>, | ||
@SyncConfigType(Frequent) private val config: SyncConfig | ||
) : ModelSync { | ||
|
||
override val name: String = "Patient Attribute" | ||
|
||
override val requiresSyncApprovedUser = true | ||
|
||
override fun push() { | ||
syncCoordinator.push(repository, config.pushBatchSize) { api.push(toRequest(it)).execute().read()!! } | ||
} | ||
|
||
override fun pull() { | ||
val batchSize = config.pullBatchSize | ||
syncCoordinator.pull(repository, lastPullToken, batchSize) { api.pull(batchSize, it).execute().read()!! } | ||
} | ||
|
||
private fun toRequest(patientAttributes: List<PatientAttribute>): PatientAttributePushRequest { | ||
val payloads = patientAttributes | ||
.map { | ||
it.run { | ||
PatientAttributePayload( | ||
uuid = uuid, | ||
patientUuid = patientUuid, | ||
userUuid = userUuid, | ||
height = reading.height, | ||
weight = reading.weight, | ||
createdAt = timestamps.createdAt, | ||
updatedAt = timestamps.updatedAt, | ||
deletedAt = timestamps.deletedAt | ||
) | ||
} | ||
} | ||
return PatientAttributePushRequest(payloads) | ||
} | ||
} |
Oops, something went wrong.