diff --git a/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/AdherenceRecordRepo.kt b/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/AdherenceRecordRepo.kt index 08832ba8c..dcf745b63 100644 --- a/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/AdherenceRecordRepo.kt +++ b/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/AdherenceRecordRepo.kt @@ -81,17 +81,6 @@ class AdherenceRecordRepo(httpClient: HttpClient, } } - - // TODO: Remove once this method is not longer being used by ScheduleTimelineRep -nbrown 10/17/22 - /** - * Get the locally cached [AdherenceRecord]s for the specified [instanceIds]. - */ - @Deprecated("Uses old resource based cache, needs replaced with new implementation") - fun getCachedAdherenceRecords(instanceIds: List, studyId: String) : Map> { - val list: List = database.getResourcesByIds(instanceIds, ResourceType.ADHERENCE_RECORD, studyId).mapNotNull { it.loadResource() } - return list.groupBy { it.instanceGuid } - } - /** * Download and cache locally all [AdherenceRecord]s for the given study. * For now this only requests records using the current timestamps. @@ -117,7 +106,6 @@ class AdherenceRecordRepo(httpClient: HttpClient, } suspend fun processAdherenceRecordUpdates(studyId: String) { - processUpdates(studyId) processUpdatesV2(studyId) } @@ -130,31 +118,21 @@ class AdherenceRecordRepo(httpClient: HttpClient, } private fun insertUpdate(studyId: String, adherenceRecord: AdherenceRecord, needSave: Boolean) { - //TODO: Don't overwrite records that have needSave=true -nbrown 10/10/22 - val json = Json.encodeToString(adherenceRecord) - //TODO: removing writing to resource table -nbrown 10/17/22 - val resource = Resource( - identifier = adherenceRecord.instanceGuid, - secondaryId = adherenceRecord.startedOn.toString(), - type = ResourceType.ADHERENCE_RECORD, - studyId = studyId, - json = json, - lastUpdate = Clock.System.now().toEpochMilliseconds(), - status = ResourceStatus.SUCCESS, - needSave = false) - database.insertUpdateResource(resource) - //Insert into new adherence specific table - dbQuery.insertUpdateAdherenceRecord( - studyId = studyId, - instanceGuid = adherenceRecord.instanceGuid, - startedOn = adherenceRecord.startedOn.toString(), - finishedOn = adherenceRecord.finishedOn?.toString(), - declined = adherenceRecord.declined, - adherenceEventTimestamp = adherenceRecord.eventTimestamp, - adherenceJson = Json.encodeToString(adherenceRecord), - status = ResourceStatus.SUCCESS, - needSave = needSave - ) + // Check that update is a local change or will not overwrite local change + if (needSave || dbQuery.getAdherence(adherenceRecord.instanceGuid, adherenceRecord.startedOn.toString()).executeAsOneOrNull()?.needSave != true) { + //Insert into new adherence specific table + dbQuery.insertUpdateAdherenceRecord( + studyId = studyId, + instanceGuid = adherenceRecord.instanceGuid, + startedOn = adherenceRecord.startedOn.toString(), + finishedOn = adherenceRecord.finishedOn?.toString(), + declined = adherenceRecord.declined, + adherenceEventTimestamp = adherenceRecord.eventTimestamp, + adherenceJson = Json.encodeToString(adherenceRecord), + status = ResourceStatus.SUCCESS, + needSave = needSave + ) + } } @@ -167,67 +145,10 @@ class AdherenceRecordRepo(httpClient: HttpClient, Logger.i("Updating adherence record:${adherenceRecord.instanceGuid}, finished:${adherenceRecord.finishedOn}") insertUpdate(studyId, record, needSave = true) backgroundScope.launch { - processUpdates(studyId) processUpdatesV2(studyId) } } - /** - * Save any locally cached [AdherenceRecord]s that haven't been uploaded to Bridge server. - * Any failures will remain in a needSave state and be tried again the next time. - */ - //TODO: Remove in future release -nbrown 10/17/22 - private suspend fun processUpdates(studyId: String) { - val resourcesToUpload = database.getResourcesNeedSave(ResourceType.ADHERENCE_RECORD, studyId) - if (resourcesToUpload.isEmpty()) return - val chunkedList = resourcesToUpload.chunked(25) - for (resources in chunkedList) { - processUpdates(studyId, resources) - } - } - - private suspend fun processUpdates(studyId: String, resourcesToUpload: List) { - val records: List = resourcesToUpload.mapNotNull { it.loadResource() } - if (records.isEmpty()) return - var status = ResourceStatus.FAILED - var needSave = true - try { - scheduleV2Api.updateAdherenceRecords(studyId, records) - status = ResourceStatus.SUCCESS - needSave = false - } catch (throwable: Throwable) { - println(throwable) - when (throwable) { - is ResponseException -> { - when (throwable.response.status) { - HttpStatusCode.Unauthorized -> { - // 401 unauthorized - // User hasn't authenticated or re-auth has failed. - } - HttpStatusCode.Gone -> { - //410 Gone - // The version of the client making the request no longer has access to this service. - // The user must update their app in order to continue using Bridge. - } - HttpStatusCode.PreconditionFailed -> { - //412 Not consented - } - } - } - - is UnresolvedAddressException -> { - //Internet connection error - status = ResourceStatus.RETRY - } - } - } - for (resource in resourcesToUpload) { - val toUpdate = resource.copy(status = status, needSave = needSave) - database.insertUpdateResource(toUpdate) - } - - } - /** * Save any locally cached [AdherenceRecord]s that haven't been uploaded to Bridge server. * Any failures will remain in a needSave state and be tried again the next time. diff --git a/bridge-client/src/commonTest/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/AdherenceRecordRepoTest.kt b/bridge-client/src/commonTest/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/AdherenceRecordRepoTest.kt index d2ad12eff..3af7508e3 100644 --- a/bridge-client/src/commonTest/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/AdherenceRecordRepoTest.kt +++ b/bridge-client/src/commonTest/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/AdherenceRecordRepoTest.kt @@ -1,14 +1,26 @@ package org.sagebionetworks.bridge.kmm.shared.repo +import io.ktor.client.engine.config +import io.ktor.client.engine.mock.MockEngine +import io.ktor.client.engine.mock.respondError +import io.ktor.client.engine.mock.respondOk +import io.ktor.http.HttpStatusCode import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull import kotlinx.datetime.TimeZone +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.jsonPrimitive import org.sagebionetworks.bridge.kmm.shared.BaseTest import org.sagebionetworks.bridge.kmm.shared.cache.ResourceDatabaseHelper +import org.sagebionetworks.bridge.kmm.shared.cache.ResourceStatus import org.sagebionetworks.bridge.kmm.shared.cache.ResourceType +import org.sagebionetworks.bridge.kmm.shared.getJsonReponseHandler import org.sagebionetworks.bridge.kmm.shared.getTestClient import org.sagebionetworks.bridge.kmm.shared.models.UploadFile import org.sagebionetworks.bridge.kmm.shared.models.UploadMetadata @@ -44,23 +56,61 @@ class AdherenceRecordRepoTest: BaseTest() { runTest { val studyId = "testId" val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) - val repo = AdherenceRecordRepo(getTestClient(json), null, ResourceDatabaseHelper(testDatabaseDriver()), scope) + val mockEngine = MockEngine.config { + // 1 - Load adherence call + addHandler ( + getJsonReponseHandler(json) + ) + // 2 - Process updates call needs to fail so we still have pending local changes + addHandler { respondError(HttpStatusCode.GatewayTimeout) } + // 3 - Second Load adherence call + addHandler ( + getJsonReponseHandler(json) + ) + reuseHandlers = false + } + val testClient = getTestClient(mockEngine) + + val repo = AdherenceRecordRepo(testClient, null, ResourceDatabaseHelper(testDatabaseDriver()), scope) assertTrue(repo.loadRemoteAdherenceRecords(studyId)) - val adherenceRecord = repo.getCachedAdherenceRecord(instanceGuid, startedOn) + var adherenceRecord = repo.getCachedAdherenceRecord(instanceGuid, startedOn) assertNotNull(adherenceRecord) - val recordMap = repo.getCachedAdherenceRecords(listOf(instanceGuid), studyId) + val recordMap = repo.getAllCachedAdherenceRecords(studyId).first() assertEquals(1, recordMap.size) - val recordList = recordMap.get(instanceGuid) + val recordList = recordMap[instanceGuid] assertNotNull(recordList) - val record = recordList.get(0) + val record = recordList[0] assertNotNull(record) assertEquals(instanceGuid, record.instanceGuid) + + val updatedRecord = record.copy(clientData = JsonPrimitive("Test data")) + repo.database.database.participantScheduleQueries.insertUpdateAdherenceRecord( + studyId = studyId, + instanceGuid = updatedRecord.instanceGuid, + startedOn = updatedRecord.startedOn.toString(), + finishedOn = updatedRecord.finishedOn?.toString(), + declined = updatedRecord.declined, + adherenceEventTimestamp = updatedRecord.eventTimestamp, + adherenceJson = Json.encodeToString(updatedRecord), + status = ResourceStatus.SUCCESS, + needSave = true + ) + var dbRecord = repo.database.database.participantScheduleQueries.getAdherence(instanceGuid, startedOn).executeAsOne() + assertTrue(dbRecord.needSave) + adherenceRecord = repo.getCachedAdherenceRecord(instanceGuid, startedOn) + assertEquals("\"Test data\"", adherenceRecord!!.clientData.toString()) + + // Trigger a load of data + assertTrue(repo.loadRemoteAdherenceRecords(studyId)) + // Check that local changes did not get overwritten + dbRecord = repo.database.database.participantScheduleQueries.getAdherence(instanceGuid, startedOn).executeAsOne() + assertTrue(dbRecord.needSave) + adherenceRecord = repo.getCachedAdherenceRecord(instanceGuid, startedOn) + assertEquals("\"Test data\"", adherenceRecord!!.clientData.toString()) + val db = repo.database - val resource = db.getResource(instanceGuid, ResourceType.ADHERENCE_RECORD, studyId) - assertNotNull(resource) - assertFalse(resource.needSave) db.clearDatabase() } }