From 0aeb9dcfb8df15a5c69f580744230723744162cf Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 16 Feb 2026 14:00:53 +0200 Subject: [PATCH 1/7] [MS-1346] Identificaiton response now contains scanned external credential data --- .../feature/clientapi/ClientApiViewModel.kt | 1 + .../mappers/response/LibSimprintsResponseMapper.kt | 2 +- .../usecases/MapRefusalOrErrorResultUseCase.kt | 9 ++++++++- .../response/CreateIdentifyResponseUseCase.kt | 13 +++++++++++++ .../infra/orchestration/data/ActionResponse.kt | 1 + .../data/responses/AppIdentifyResponse.kt | 1 + 6 files changed, 25 insertions(+), 2 deletions(-) diff --git a/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt b/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt index 63f2395485..8a169f1fcc 100644 --- a/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt +++ b/feature/client-api/src/main/java/com/simprints/feature/clientapi/ClientApiViewModel.kt @@ -148,6 +148,7 @@ class ClientApiViewModel @Inject internal constructor( sessionId = currentSessionId, identifications = identifyResponse.identifications, isMultiFactorIdEnabled = identifyResponse.isMultiFactorIdEnabled, + scannedCredential = identifyResponse.scannedCredential, ), ), ) diff --git a/feature/client-api/src/main/java/com/simprints/feature/clientapi/mappers/response/LibSimprintsResponseMapper.kt b/feature/client-api/src/main/java/com/simprints/feature/clientapi/mappers/response/LibSimprintsResponseMapper.kt index fe8243d9b7..cf98eb962e 100644 --- a/feature/client-api/src/main/java/com/simprints/feature/clientapi/mappers/response/LibSimprintsResponseMapper.kt +++ b/feature/client-api/src/main/java/com/simprints/feature/clientapi/mappers/response/LibSimprintsResponseMapper.kt @@ -73,7 +73,7 @@ internal class LibSimprintsResponseMapper @Inject constructor( .toJson(), ) } - } + }.appendExternalCredential(response.scannedCredential.takeIf { response.isMultiFactorIdEnabled }) is ActionResponse.ConfirmActionResponse -> { bundleOf( diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/MapRefusalOrErrorResultUseCase.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/MapRefusalOrErrorResultUseCase.kt index 619aad1d04..9aa701fcf6 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/MapRefusalOrErrorResultUseCase.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/MapRefusalOrErrorResultUseCase.kt @@ -59,7 +59,14 @@ internal class MapRefusalOrErrorResultUseCase @Inject constructor( is ValidateSubjectPoolResult -> result .takeUnless { it.isValid } - ?.let { AppIdentifyResponse(emptyList(), eventRepository.getCurrentSessionScope().id, isMultiFactorIdEnabled = false) } + ?.let { + AppIdentifyResponse( + identifications = emptyList(), + sessionId = eventRepository.getCurrentSessionScope().id, + isMultiFactorIdEnabled = false, + scannedCredential = null, + ) + } is SelectSubjectAgeGroupResult -> result diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt index 62e759acf9..e2acb59cb4 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt @@ -1,12 +1,14 @@ package com.simprints.feature.orchestrator.usecases.response import com.simprints.feature.externalcredential.ExternalCredentialSearchResult +import com.simprints.feature.externalcredential.screens.search.model.ScannedCredential import com.simprints.infra.config.store.models.DecisionPolicy import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.events.session.SessionEventRepository import com.simprints.infra.matching.FaceMatchResult import com.simprints.infra.matching.FingerprintMatchResult import com.simprints.infra.matching.MatchResultItem +import com.simprints.infra.orchestration.data.responses.AppExternalCredential import com.simprints.infra.orchestration.data.responses.AppIdentifyResponse import com.simprints.infra.orchestration.data.responses.AppMatchResult import com.simprints.infra.orchestration.data.responses.AppResponse @@ -45,10 +47,21 @@ internal class CreateIdentifyResponseUseCase @Inject constructor( .distinctBy(AppMatchResult::guid) .take(projectConfiguration.identification.maxNbOfReturnedCandidates) + val credentialResult = results.filterIsInstance(ExternalCredentialSearchResult::class.java).lastOrNull() + val credentialValue = credentialResult?.scannedCredential.toAppCredential() return AppIdentifyResponse( sessionId = currentSessionId, isMultiFactorIdEnabled = isMultiFactorIdEnabled, identifications = identifications, + scannedCredential = credentialValue, + ) + } + + private fun ScannedCredential?.toAppCredential(): AppExternalCredential? = this?.let { scannedCredential -> + AppExternalCredential( + id = scannedCredential.credentialScanId, + value = scannedCredential.scannedValue, + type = scannedCredential.credentialType, ) } diff --git a/infra/orchestrator-data/src/main/java/com/simprints/infra/orchestration/data/ActionResponse.kt b/infra/orchestrator-data/src/main/java/com/simprints/infra/orchestration/data/ActionResponse.kt index cbb24033ad..59f746c8ff 100644 --- a/infra/orchestrator-data/src/main/java/com/simprints/infra/orchestration/data/ActionResponse.kt +++ b/infra/orchestrator-data/src/main/java/com/simprints/infra/orchestration/data/ActionResponse.kt @@ -25,6 +25,7 @@ sealed class ActionResponse( override val sessionId: String, val identifications: List, val isMultiFactorIdEnabled: Boolean, + val scannedCredential: AppExternalCredential?, ) : ActionResponse(actionIdentifier, sessionId) @ExcludedFromGeneratedTestCoverageReports("Data struct") diff --git a/infra/orchestrator-data/src/main/java/com/simprints/infra/orchestration/data/responses/AppIdentifyResponse.kt b/infra/orchestrator-data/src/main/java/com/simprints/infra/orchestration/data/responses/AppIdentifyResponse.kt index 6b22f9f81a..21019f3402 100644 --- a/infra/orchestrator-data/src/main/java/com/simprints/infra/orchestration/data/responses/AppIdentifyResponse.kt +++ b/infra/orchestrator-data/src/main/java/com/simprints/infra/orchestration/data/responses/AppIdentifyResponse.kt @@ -11,4 +11,5 @@ data class AppIdentifyResponse( val identifications: List, val sessionId: String, val isMultiFactorIdEnabled: Boolean, + val scannedCredential: AppExternalCredential?, ) : AppResponse() From c956b366b1a3a044d1e971102a3b94ef7d5cf109 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 16 Feb 2026 15:13:46 +0200 Subject: [PATCH 2/7] [MS-1346] Renaming variables to be more consistent --- .../usecases/response/CreateIdentifyResponseUseCase.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt index e2acb59cb4..834bce4d8a 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt @@ -48,16 +48,16 @@ internal class CreateIdentifyResponseUseCase @Inject constructor( .take(projectConfiguration.identification.maxNbOfReturnedCandidates) val credentialResult = results.filterIsInstance(ExternalCredentialSearchResult::class.java).lastOrNull() - val credentialValue = credentialResult?.scannedCredential.toAppCredential() + val externalCredential = credentialResult?.scannedCredential.toAppExternalCredential() return AppIdentifyResponse( sessionId = currentSessionId, isMultiFactorIdEnabled = isMultiFactorIdEnabled, identifications = identifications, - scannedCredential = credentialValue, + scannedCredential = externalCredential, ) } - private fun ScannedCredential?.toAppCredential(): AppExternalCredential? = this?.let { scannedCredential -> + private fun ScannedCredential?.toAppExternalCredential(): AppExternalCredential? = this?.let { scannedCredential -> AppExternalCredential( id = scannedCredential.credentialScanId, value = scannedCredential.scannedValue, From e4d50ccba341357beef01c36c43e78be14b2f832 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 16 Feb 2026 15:36:45 +0200 Subject: [PATCH 3/7] [MS-1346] Fixing tests --- .../orchestrator/usecases/AddCallbackEventUseCaseTest.kt | 1 + .../orchestrator/usecases/UpdateDailyActivityUseCaseTest.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/AddCallbackEventUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/AddCallbackEventUseCaseTest.kt index fae3556619..9c72186a3d 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/AddCallbackEventUseCaseTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/AddCallbackEventUseCaseTest.kt @@ -71,6 +71,7 @@ class AddCallbackEventUseCaseTest { listOf(AppMatchResult("guid", 0, AppMatchConfidence.HIGH)), "sessionId", isMultiFactorIdEnabled = false, + scannedCredential = null, ), ) diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/UpdateDailyActivityUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/UpdateDailyActivityUseCaseTest.kt index 5d83449464..916f9d8a36 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/UpdateDailyActivityUseCaseTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/UpdateDailyActivityUseCaseTest.kt @@ -59,7 +59,7 @@ class UpdateDailyActivityUseCaseTest { @Test fun `Update daily activity on identify response`() = runTest { - useCase(AppIdentifyResponse(emptyList(), "guid", isMultiFactorIdEnabled = false)) + useCase(AppIdentifyResponse(emptyList(), "guid", isMultiFactorIdEnabled = false, scannedCredential = null)) coVerify { recentUserActivityManager.updateRecentUserActivity(any()) } } From 11b24df8f25e4eeeac7b80651322226f79d38d64 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 16 Feb 2026 16:05:28 +0200 Subject: [PATCH 4/7] [MS-1346] Fixing tests, removing unnecessary variables --- .../usecases/response/CreateIdentifyResponseUseCase.kt | 8 ++++++-- .../response/CreateIdentifyResponseUseCaseTest.kt | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt index 834bce4d8a..8dbe30fba5 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCase.kt @@ -47,8 +47,12 @@ internal class CreateIdentifyResponseUseCase @Inject constructor( .distinctBy(AppMatchResult::guid) .take(projectConfiguration.identification.maxNbOfReturnedCandidates) - val credentialResult = results.filterIsInstance(ExternalCredentialSearchResult::class.java).lastOrNull() - val externalCredential = credentialResult?.scannedCredential.toAppExternalCredential() + val externalCredential = results + .filterIsInstance(ExternalCredentialSearchResult::class.java) + .lastOrNull() + ?.scannedCredential + .toAppExternalCredential() + return AppIdentifyResponse( sessionId = currentSessionId, isMultiFactorIdEnabled = isMultiFactorIdEnabled, diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCaseTest.kt index ce45e0c6bd..1d11320e17 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCaseTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/CreateIdentifyResponseUseCaseTest.kt @@ -259,6 +259,7 @@ class CreateIdentifyResponseUseCaseTest { results = listOf( mockk { every { matchResults } returns fingerprintMatches + faceMatches + every { scannedCredential } returns null }, ), ) @@ -304,6 +305,7 @@ class CreateIdentifyResponseUseCaseTest { results = listOf( mockk { every { matchResults } returns credentialFaceMatches + every { scannedCredential } returns null }, FaceMatchResult( listOf( @@ -355,6 +357,7 @@ class CreateIdentifyResponseUseCaseTest { results = listOf( mockk { every { matchResults } returns credentialFingerprintMatches + every { scannedCredential } returns null }, FingerprintMatchResult( listOf( @@ -430,6 +433,7 @@ class CreateIdentifyResponseUseCaseTest { results = listOf( mockk { every { matchResults } returns credentialFaceMatches + every { scannedCredential } returns null }, faceMatchResults, ), From 0f72fe89fe78d623527f6b72784a2a3c161e7a69 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 16 Feb 2026 16:17:01 +0200 Subject: [PATCH 5/7] [MS-1346] Fixing tests --- .../clientapi/mappers/response/CommCareResponseMapperTest.kt | 1 + .../mappers/response/LibSimprintsResponseMapperTest.kt | 5 +++++ .../clientapi/mappers/response/OdkResponseMapperTest.kt | 2 ++ 3 files changed, 8 insertions(+) diff --git a/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/CommCareResponseMapperTest.kt b/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/CommCareResponseMapperTest.kt index f9abf3fb49..53f615d29c 100644 --- a/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/CommCareResponseMapperTest.kt +++ b/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/CommCareResponseMapperTest.kt @@ -61,6 +61,7 @@ class CommCareResponseMapperTest { ), ), isMultiFactorIdEnabled = false, + scannedCredential = null, ), ) diff --git a/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/LibSimprintsResponseMapperTest.kt b/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/LibSimprintsResponseMapperTest.kt index 5d3140035f..0345d084ab 100644 --- a/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/LibSimprintsResponseMapperTest.kt +++ b/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/LibSimprintsResponseMapperTest.kt @@ -93,6 +93,7 @@ class LibSimprintsResponseMapperTest { ), ), isMultiFactorIdEnabled = false, + scannedCredential = null, ), ) @@ -121,6 +122,7 @@ class LibSimprintsResponseMapperTest { ), ), isMultiFactorIdEnabled = false, + scannedCredential = null, ), ) @@ -469,6 +471,7 @@ class LibSimprintsResponseMapperTest { sessionId = "sessionId", identifications = listOf(identification1, identification2), isMultiFactorIdEnabled = true, + scannedCredential = null, ), ) @@ -496,6 +499,7 @@ class LibSimprintsResponseMapperTest { ), ), isMultiFactorIdEnabled = true, + scannedCredential = null, ), ) @@ -523,6 +527,7 @@ class LibSimprintsResponseMapperTest { ), ), isMultiFactorIdEnabled = false, + scannedCredential = null, ), ) diff --git a/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/OdkResponseMapperTest.kt b/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/OdkResponseMapperTest.kt index 6b1349d889..2459de0d2e 100644 --- a/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/OdkResponseMapperTest.kt +++ b/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/OdkResponseMapperTest.kt @@ -55,6 +55,7 @@ class OdkResponseMapperTest { ), ), isMultiFactorIdEnabled = false, + scannedCredential = null, ), ) @@ -75,6 +76,7 @@ class OdkResponseMapperTest { sessionId = "sessionId", identifications = listOf(), isMultiFactorIdEnabled = false, + scannedCredential = null, ), ) From 6adb36f2d1ad0fd7bfc75e5bbbe503975e02530a Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 16 Feb 2026 17:25:28 +0200 Subject: [PATCH 6/7] [MS-1346] Fixing tests --- .../com/simprints/feature/clientapi/ClientApiViewModelTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt b/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt index 4fe7c5a89b..c99bc9824d 100644 --- a/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt +++ b/feature/client-api/src/test/java/com/simprints/feature/clientapi/ClientApiViewModelTest.kt @@ -177,6 +177,7 @@ internal class ClientApiViewModelTest { mockk { every { identifications } returns emptyList() every { isMultiFactorIdEnabled } returns false + every { scannedCredential } returns null }, ) From e87e7a89b0fa5e56079aa99afd2aa285c8d636f5 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 16 Feb 2026 17:57:06 +0200 Subject: [PATCH 7/7] [MS-1346] Adding test coverage for credential response in Identification response --- .../LibSimprintsResponseMapperTest.kt | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/LibSimprintsResponseMapperTest.kt b/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/LibSimprintsResponseMapperTest.kt index 0345d084ab..4aa18e2708 100644 --- a/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/LibSimprintsResponseMapperTest.kt +++ b/feature/client-api/src/test/java/com/simprints/feature/clientapi/mappers/response/LibSimprintsResponseMapperTest.kt @@ -1,5 +1,6 @@ package com.simprints.feature.clientapi.mappers.response +import android.os.Bundle import androidx.test.ext.junit.runners.* import com.google.common.truth.Truth.* import com.simprints.core.domain.externalcredential.ExternalCredentialType @@ -16,6 +17,7 @@ import com.simprints.feature.clientapi.mappers.response.LibSimprintsResponseMapp import com.simprints.feature.clientapi.mappers.response.LibSimprintsResponseMapper.Companion.SCANNED_CREDENTIAL_TYPE import com.simprints.feature.clientapi.mappers.response.LibSimprintsResponseMapper.Companion.SCANNED_CREDENTIAL_VALUE import com.simprints.infra.orchestration.data.ActionResponse +import com.simprints.infra.orchestration.data.responses.AppExternalCredential import com.simprints.infra.orchestration.data.responses.AppMatchResult import com.simprints.libsimprints.Constants import com.simprints.libsimprints.contracts.VersionsList @@ -536,6 +538,76 @@ class LibSimprintsResponseMapperTest { ) } + @Test + fun `when MFID is enabled, identify response contains scanned credential`() { + val expectedValue = "expectedValue".asTokenizableRaw() + val expectedType = ExternalCredentialType.NHISCard + val expectedJson = "{\"$SCANNED_CREDENTIAL_VALUE\":\"$expectedValue\",\"$SCANNED_CREDENTIAL_TYPE\":\"$expectedType\"}" + val scannedCredential = mockk { + every { value } returns expectedValue + every { type } returns expectedType + } + + val extras = mapper( + createIdentifyActionResponse( + isMultiFactorIdEnabled = true, + scannedCredential = scannedCredential, + ), + ) + + assertCommonMfidIdentifyFields(extras) + assertThat(extras.getString(SCANNED_CREDENTIAL)).isEqualTo(expectedJson) + } + + @Test + fun `when MFID is disabled, identify response does not contain credential`() { + val expectedValue = "expectedValue".asTokenizableRaw() + val expectedType = ExternalCredentialType.NHISCard + val scannedCredential = mockk { + every { value } returns expectedValue + every { type } returns expectedType + } + + val extras = mapper( + createIdentifyActionResponse( + isMultiFactorIdEnabled = false, + scannedCredential = scannedCredential, + ), + ) + + assertCommonMfidIdentifyFields(extras) + assertThat(extras.keySet()).doesNotContain(SCANNED_CREDENTIAL) + } + + // Helper functions + private fun createIdentifyActionResponse( + sessionId: String = "sessionId", + isMultiFactorIdEnabled: Boolean, + scannedCredential: AppExternalCredential? = null, + identifications: List = listOf( + AppMatchResult( + guid = "guid-1", + confidenceScore = 100, + matchConfidence = AppMatchConfidence.MEDIUM, + isLinkedToScannedCredential = true, + isCredentialVerified = true, + ), + ), + ) = ActionResponse.IdentifyActionResponse( + actionIdentifier = IdentifyRequestActionFactory.getIdentifier(), + sessionId = sessionId, + identifications = identifications, + isMultiFactorIdEnabled = isMultiFactorIdEnabled, + scannedCredential = scannedCredential, + ) + + private fun assertCommonMfidIdentifyFields(extras: Bundle) { + assertThat(extras.getString(Constants.SIMPRINTS_SESSION_ID)).isEqualTo("sessionId") + assertThat(extras.getString(Constants.SIMPRINTS_DEVICE_ID)).isEqualTo("deviceId") + assertThat(extras.getString(Constants.SIMPRINTS_APP_VERSION_NAME)).isEqualTo("appVersionName") + assertThat(extras.getBoolean(Constants.SIMPRINTS_BIOMETRICS_COMPLETE_CHECK)).isTrue() + } + private fun AppMatchResult.toResponseJson(): String { val jsonBuilder = StringBuilder() jsonBuilder.append("{\"guid\":\"$guid\"")