Skip to content

Commit

Permalink
Merge branch 'main' into security-automation-additions-latest-main
Browse files Browse the repository at this point in the history
  • Loading branch information
Rkareko authored Aug 9, 2023
2 parents a399ae6 + 09446b7 commit df74831
Show file tree
Hide file tree
Showing 15 changed files with 372 additions and 173 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import okhttp3.RequestBody.Companion.toRequestBody
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.Binary
import org.hl7.fhir.r4.model.Bundle
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent
import org.hl7.fhir.r4.model.Composition
import org.hl7.fhir.r4.model.ListResource
import org.hl7.fhir.r4.model.Resource
Expand Down Expand Up @@ -83,7 +82,7 @@ constructor(
val configCacheMap = mutableMapOf<String, Configuration>()
val localizationHelper: LocalizationHelper by lazy { LocalizationHelper(this) }
private val supportedFileExtensions = listOf("json", "properties")
private var isNonProxy_ = BuildConfig.IS_NON_PROXY_APK
private var _isNonProxy = BuildConfig.IS_NON_PROXY_APK

/**
* Retrieve configuration for the provided [ConfigType]. The JSON retrieved from [configsJsonMap]
Expand Down Expand Up @@ -131,7 +130,7 @@ constructor(
locale = Locale.getDefault(),
template = it,
)
.decodeJson<T>()
.decodeJson()
}

/**
Expand Down Expand Up @@ -229,40 +228,44 @@ constructor(
val parsedAppId = appId.substringBefore(TYPE_REFERENCE_DELIMITER).trim()
if (loadFromAssets) {
try {
context.assets
.open(String.format(COMPOSITION_CONFIG_PATH, parsedAppId))
.bufferedReader()
.readText()
.decodeResourceFromString<Composition>()
.run {
val iconConfigs =
retrieveCompositionSections().filter {
it.focus.hasIdentifier() && isIconConfig(it.focus.identifier.value)
}
if (iconConfigs.isNotEmpty()) {
val ids = iconConfigs.joinToString(DEFAULT_STRING_SEPARATOR) { it.focus.extractId() }
fhirResourceDataSource
.getResource(
"${ResourceType.Binary.name}?$ID=$ids&_count=$DEFAULT_COUNT",
)
.entry
.forEach { addOrUpdate(it.resource) }
val localCompositionResource =
context.assets
.open(String.format(COMPOSITION_CONFIG_PATH, parsedAppId))
.bufferedReader()
.readText()
.decodeResourceFromString<Composition>()

addOrUpdate(localCompositionResource)

localCompositionResource.run {
val iconConfigs =
retrieveCompositionSections().filter {
it.focus.hasIdentifier() && isIconConfig(it.focus.identifier.value)
}
populateConfigurationsMap(
composition = this,
loadFromAssets = true,
appId = parsedAppId,
configsLoadedCallback = configsLoadedCallback,
context = context,
)
if (iconConfigs.isNotEmpty()) {
val ids = iconConfigs.joinToString(DEFAULT_STRING_SEPARATOR) { it.focus.extractId() }
fhirResourceDataSource
.getResource(
"${ResourceType.Binary.name}?$ID=$ids&_count=$DEFAULT_COUNT",
)
.entry
.forEach { addOrUpdate(it.resource) }
}
populateConfigurationsMap(
composition = this,
loadFromAssets = true,
appId = parsedAppId,
configsLoadedCallback = configsLoadedCallback,
context = context,
)
}
} catch (fileNotFoundException: FileNotFoundException) {
Timber.e("Missing app configs for app ID: $parsedAppId", fileNotFoundException)
withContext(dispatcherProvider.main()) { configsLoadedCallback(false) }
}
} else {
fhirEngine.searchCompositionByIdentifier(appId)?.run {
populateConfigurationsMap(context, this, false, appId, configsLoadedCallback)
fhirEngine.searchCompositionByIdentifier(parsedAppId)?.run {
populateConfigurationsMap(context, this, false, parsedAppId, configsLoadedCallback)
}
}
}
Expand Down Expand Up @@ -300,8 +303,14 @@ constructor(
val configIdentifier = it.focus.identifier.value
val referenceResourceType = it.focus.reference.substringBefore(TYPE_REFERENCE_DELIMITER)
if (isAppConfig(referenceResourceType) && !isIconConfig(configIdentifier)) {
val configBinary = fhirEngine.get<Binary>(it.focus.extractId())
configsJsonMap[configIdentifier] = configBinary.content.decodeToString()
val extractedId = it.focus.extractId()
try {
val configBinary = fhirEngine.get<Binary>(extractedId)
configsJsonMap[configIdentifier] = configBinary.content.decodeToString()
} catch (resourceNotFoundException: ResourceNotFoundException) {
Timber.e("Missing Binary file with ID :$extractedId")
withContext(dispatcherProvider.main()) { configsLoadedCallback(false) }
}
}
}
}
Expand Down Expand Up @@ -356,13 +365,16 @@ constructor(
*/
@Throws(UnknownHostException::class, HttpException::class)
suspend fun fetchNonWorkflowConfigResources() {
sharedPreferencesHelper.read(SharedPreferenceKey.APP_ID.name, null)?.let { appId: String ->
fhirEngine.searchCompositionByIdentifier(appId)?.let { composition ->
sharedPreferencesHelper.read(SharedPreferenceKey.APP_ID.name, null)?.let { appId ->
val parsedAppId = appId.substringBefore(TYPE_REFERENCE_DELIMITER).trim()
fhirEngine.searchCompositionByIdentifier(parsedAppId)?.let { composition ->
composition
.retrieveCompositionSections()
.groupBy { it.focus.reference?.split(TYPE_REFERENCE_DELIMITER)?.firstOrNull() ?: "" }
.filter {
it.key in
.groupBy { section ->
section.focus.reference?.split(TYPE_REFERENCE_DELIMITER)?.firstOrNull() ?: ""
}
.filter { entry ->
entry.key in
listOf(
ResourceType.Questionnaire.name,
ResourceType.StructureMap.name,
Expand Down Expand Up @@ -435,22 +447,25 @@ constructor(
resourceIdList: List<String>,
): Bundle {
val resultBundle =
fhirResourceDataSource.post(
"",
generateRequestBundle(resourceType, resourceIdList)
.encodeResourceToString()
.toRequestBody(NetworkModule.JSON_MEDIA_TYPE),
)
if (isNonProxy()) {
fhirResourceDataSourceGetBundle(resourceType, resourceIdList)
} else
fhirResourceDataSource.post(
requestBody =
generateRequestBundle(resourceType, resourceIdList)
.encodeResourceToString()
.toRequestBody(NetworkModule.JSON_MEDIA_TYPE),
)
resultBundle.entry?.forEach { bundleEntryComponent ->
when (bundleEntryComponent.resource) {
is Bundle -> {
val bundle = bundleEntryComponent.resource as Bundle
bundle.entry.forEach { entryComponent ->
when (entryComponent.resource) {
is Bundle -> {
val bundle = entryComponent.resource as Bundle
addOrUpdate(bundle)
bundle.entry.forEach { innerEntryComponent ->
val thisBundle = entryComponent.resource as Bundle
addOrUpdate(thisBundle)
thisBundle.entry.forEach { innerEntryComponent ->
saveListEntryResource(innerEntryComponent)
}
}
Expand Down Expand Up @@ -489,9 +504,9 @@ constructor(
bundle.entry.forEach { entryComponent ->
when (entryComponent.resource) {
is Bundle -> {
val bundle = entryComponent.resource as Bundle
addOrUpdate(bundle)
bundle.entry.forEach { innerEntryComponent ->
val thisBundle = entryComponent.resource as Bundle
addOrUpdate(thisBundle)
thisBundle.entry.forEach { innerEntryComponent ->
saveListEntryResource(innerEntryComponent)
}
}
Expand All @@ -509,7 +524,7 @@ constructor(
}
}

private suspend fun saveListEntryResource(entryComponent: BundleEntryComponent) {
private suspend fun saveListEntryResource(entryComponent: Bundle.BundleEntryComponent) {
addOrUpdate(entryComponent.resource)
Timber.d(
"Fetched and processed List reference ${entryComponent.resource.resourceType}/${entryComponent.resource.id}",
Expand All @@ -521,7 +536,6 @@ constructor(
* resource, or create it if not found.
*/
suspend fun <R : Resource> addOrUpdate(resource: R) {
if (resource == null) return
withContext(dispatcherProvider.io()) {
resource.updateLastUpdated()
try {
Expand Down Expand Up @@ -551,19 +565,19 @@ constructor(
}
}

@VisibleForTesting fun isNonProxy(): Boolean = isNonProxy_
@VisibleForTesting fun isNonProxy(): Boolean = _isNonProxy

@VisibleForTesting
fun setNonProxy(nonProxy: Boolean) {
isNonProxy_ = nonProxy
_isNonProxy = nonProxy
}

private fun generateRequestBundle(resourceType: String, idList: List<String>): Bundle {
val bundleEntryComponents = mutableListOf<BundleEntryComponent>()
val bundleEntryComponents = mutableListOf<Bundle.BundleEntryComponent>()

idList.forEach {
bundleEntryComponents.add(
BundleEntryComponent().apply {
Bundle.BundleEntryComponent().apply {
request =
Bundle.BundleEntryRequestComponent().apply {
url = "$resourceType/$it"
Expand All @@ -579,6 +593,29 @@ constructor(
}
}

private suspend fun fhirResourceDataSourceGetBundle(
resourceType: String,
resourceIds: List<String>,
): Bundle {
val bundleEntryComponents = mutableListOf<Bundle.BundleEntryComponent>()

resourceIds.forEach {
val responseBundle =
fhirResourceDataSource.getResource("$resourceType?${Composition.SP_RES_ID}=$it")
responseBundle?.let {
bundleEntryComponents.add(
Bundle.BundleEntryComponent().apply {
resource = responseBundle.entry?.first()?.resource
},
)
}
}
return Bundle().apply {
type = Bundle.BundleType.COLLECTION
entry = bundleEntryComponents
}
}

companion object {
const val BASE_CONFIG_PATH = "configs/%s"
const val COMPOSITION_CONFIG_PATH = "configs/%s/composition_config.json"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class FhirResourceDataSource @Inject constructor(private val resourceService: Fh
return resourceService.getResource(path)
}

suspend fun post(path: String, requestBody: RequestBody): Bundle {
suspend fun post(path: String = "", requestBody: RequestBody): Bundle {
return resourceService.post(path, requestBody)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ constructor(
val defaultRepository: DefaultRepository,
val fhirTaskUtil: FhirTaskUtil,
) {
val structureMapUtilities by lazy {
private val structureMapUtilities by lazy {
StructureMapUtilities(transformSupportServices.simpleWorkerContext, transformSupportServices)
}

Expand All @@ -84,7 +84,8 @@ constructor(
subject: Resource,
data: Bundle = Bundle(),
): CarePlan? {
return generateOrUpdateCarePlan(fhirEngine.get(planDefinitionId), subject, data)
val planDefinition = defaultRepository.loadResource<PlanDefinition>(planDefinitionId)
return planDefinition?.let { generateOrUpdateCarePlan(it, subject, data) }
}

suspend fun generateOrUpdateCarePlan(
Expand Down
2 changes: 1 addition & 1 deletion android/engine/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
<string name="replace_photo">Replace photo</string>
<string name="take_photo">Take photo</string>
<string name="edit">Edit</string>
<string name="application_not_supported">Application %1$s not found</string>
<string name="application_not_supported">Error loading application %1$s configs</string>
<string name="eCBIS_app_name" translatable="false">eCBIS</string>
<string name="invalid_login_credentials">username or password is invalid</string>
<string name="invalid_form_id">Invalid form ID attached</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ package org.smartregister.fhircore.engine.app.fakes

import androidx.test.platform.app.InstrumentationRegistry
import io.mockk.coEvery
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.spyk
import java.util.Calendar
import java.util.Date
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.serialization.json.Json
import org.hl7.fhir.r4.model.Bundle
import org.hl7.fhir.r4.model.CarePlan
Expand All @@ -49,9 +52,22 @@ object Faker {
useAlternativeNames = true
}

private val testDispatcher = UnconfinedTestDispatcher()

val testDispatcherProvider =
object : DispatcherProvider {
override fun default() = testDispatcher

override fun io() = testDispatcher

override fun main() = testDispatcher

override fun unconfined() = testDispatcher
}

fun buildTestConfigurationRegistry(
sharedPreferencesHelper: SharedPreferencesHelper = mockk(),
dispatcherProvider: DispatcherProvider = mockk(),
dispatcherProvider: DispatcherProvider = testDispatcherProvider,
): ConfigurationRegistry {
val fhirResourceService = mockk<FhirResourceService>()
val fhirResourceDataSource = spyk(FhirResourceDataSource(fhirResourceService))
Expand Down Expand Up @@ -83,6 +99,8 @@ object Faker {
),
)

coEvery { configurationRegistry.addOrUpdate(any()) } just runs

runBlocking {
configurationRegistry.loadConfigurations(
appId = APP_DEBUG,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.spyk
import javax.inject.Inject
import kotlinx.coroutines.test.runTest
import okhttp3.RequestBody
import org.hl7.fhir.r4.model.Binary
Expand Down Expand Up @@ -75,19 +74,17 @@ class ConfigurationRegistryTest : RobolectricTest() {
@kotlinx.coroutines.ExperimentalCoroutinesApi
@get:Rule(order = 1)
val coroutineRule = CoroutineTestRule()

@Inject lateinit var configRegistry: ConfigurationRegistry
var fhirEngine: FhirEngine = mockk()
lateinit var context: Context
private lateinit var fhirResourceDataSource: FhirResourceDataSource
private val fhirEngine: FhirEngine = mockk()
private val context: Context = ApplicationProvider.getApplicationContext()
private val fhirResourceService = mockk<FhirResourceService>()
private lateinit var fhirResourceDataSource: FhirResourceDataSource
private lateinit var configRegistry: ConfigurationRegistry

@Before
@kotlinx.coroutines.ExperimentalCoroutinesApi
fun setUp() {
hiltRule.inject()
fhirResourceDataSource = spyk(FhirResourceDataSource(fhirResourceService))
context = ApplicationProvider.getApplicationContext()
val sharedPreferencesHelper =
SharedPreferencesHelper(context, GsonBuilder().setLenient().create())
configRegistry =
Expand All @@ -99,6 +96,7 @@ class ConfigurationRegistryTest : RobolectricTest() {
AppConfigService(context),
Faker.json,
)
configRegistry.setNonProxy(false)
Assert.assertNotNull(configRegistry)
}

Expand Down Expand Up @@ -329,7 +327,7 @@ class ConfigurationRegistryTest : RobolectricTest() {
coEvery {
fhirResourceDataSource.getResource("$resourceKey?_id=$resourceId&_count=200")
} returns bundle
coEvery { fhirResourceDataSource.post(any(), any()) } returns bundle
coEvery { fhirResourceDataSource.getResource(any()) } returns bundle
runTest {
configRegistry.fhirEngine.create(composition)
configRegistry.setNonProxy(true)
Expand Down
Loading

0 comments on commit df74831

Please sign in to comment.