From 4eb2b4c368c963324ad4344d946c6341de47c1ee Mon Sep 17 00:00:00 2001 From: Melad Raouf Date: Mon, 13 Oct 2025 10:49:53 +0100 Subject: [PATCH 1/6] [MS-1200] Refactor RecyclerView height adjustment to use lifecycle-aware coroutine --- .../settings/syncinfo/SyncInfoFragment.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt index bb5c777d72..f156178d1c 100644 --- a/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt +++ b/feature/dashboard/src/main/java/com/simprints/feature/dashboard/settings/syncinfo/SyncInfoFragment.kt @@ -352,12 +352,14 @@ internal class SyncInfoFragment : Fragment(R.layout.fragment_sync_info) { moduleCountAdapter.submitList(moduleCountsForAdapter) // RecyclerView height fix (wrong height may be caused by ConstraintLayout in parent views) - binding.selectedModulesView.post { - val itemHeight = resources.getDimensionPixelSize(R.dimen.module_item_height) - val itemCount = moduleCountsForAdapter.size.coerceAtMost(MAX_MODULE_LIST_HEIGHT_ITEMS) - binding.selectedModulesView.apply { - layoutParams = layoutParams.apply { - height = itemHeight * itemCount + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + val itemHeight = resources.getDimensionPixelSize(R.dimen.module_item_height) + val itemCount = moduleCountsForAdapter.size.coerceAtMost(MAX_MODULE_LIST_HEIGHT_ITEMS) + binding.selectedModulesView.apply { + layoutParams = layoutParams.apply { + height = itemHeight * itemCount + } } } } From 24e48f8f91bbff090b5397ef7f0b0b742fdc6f2b Mon Sep 17 00:00:00 2001 From: Marinov Date: Mon, 13 Oct 2025 18:41:08 +0300 Subject: [PATCH 2/6] Log full filename instead of just last char --- .../infra/images/remote/firebase/FirebaseSampleUploader.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt b/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt index 9017636d1b..dd7c17b7ac 100644 --- a/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt +++ b/infra/images/src/main/java/com/simprints/infra/images/remote/firebase/FirebaseSampleUploader.kt @@ -17,7 +17,6 @@ import com.simprints.infra.images.model.SecuredImageRef import com.simprints.infra.images.remote.SampleUploader import com.simprints.infra.images.usecase.SamplePathConverter import com.simprints.infra.logging.LoggingConstants.CrashReportTag.SAMPLE_UPLOAD -import com.simprints.infra.logging.LoggingConstants.CrashReportTag.SYNC import com.simprints.infra.logging.Simber import kotlinx.coroutines.tasks.await import java.io.FileInputStream @@ -103,7 +102,7 @@ internal class FirebaseSampleUploader @Inject constructor( val fileRef = imageRef.relativePath.parts .fold(rootRef) { ref, pathPart -> ref.child(pathPart) } - Simber.i("Uploading ${fileRef.path.last()}", tag = SAMPLE_UPLOAD) + Simber.i("Uploading ${imageRef.relativePath.parts.last()}", tag = SAMPLE_UPLOAD) return if (metadata.isEmpty()) { fileRef.putStream(imageStream).await() From bcbcf3bdc4d24e7512adaa345b954f9b59b2fb8e Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 15 Oct 2025 15:18:29 +0300 Subject: [PATCH 3/6] Expand step param and result integration tests to ensure all classes serialise correctly --- .../face/capture/FaceCaptureResult.kt | 27 +- .../feature/orchestrator/steps/Step.kt | 8 + .../cache/OrchestratorCacheIntegrationTest.kt | 281 +++++++++++++++++- .../capture/FingerprintCaptureResult.kt | 29 +- .../domain/models/BiometricDataSource.kt | 2 +- 5 files changed, 332 insertions(+), 15 deletions(-) diff --git a/face/capture/src/main/java/com/simprints/face/capture/FaceCaptureResult.kt b/face/capture/src/main/java/com/simprints/face/capture/FaceCaptureResult.kt index 23cd5993dc..d12839ee2c 100644 --- a/face/capture/src/main/java/com/simprints/face/capture/FaceCaptureResult.kt +++ b/face/capture/src/main/java/com/simprints/face/capture/FaceCaptureResult.kt @@ -1,6 +1,7 @@ package com.simprints.face.capture import androidx.annotation.Keep +import com.simprints.core.ExcludedFromGeneratedTestCoverageReports import com.simprints.core.domain.step.StepResult import com.simprints.infra.images.model.SecuredImageRef @@ -22,5 +23,29 @@ data class FaceCaptureResult( val template: ByteArray, val imageRef: SecuredImageRef?, val format: String, - ) : StepResult + ) : StepResult { + @ExcludedFromGeneratedTestCoverageReports("Generated code") + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Sample + + if (faceId != other.faceId) return false + if (!template.contentEquals(other.template)) return false + if (imageRef != other.imageRef) return false + if (format != other.format) return false + + return true + } + + @ExcludedFromGeneratedTestCoverageReports("Generated code") + override fun hashCode(): Int { + var result = faceId.hashCode() + result = 31 * result + template.contentHashCode() + result = 31 * result + (imageRef?.hashCode() ?: 0) + result = 31 * result + format.hashCode() + return result + } + } } diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/steps/Step.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/steps/Step.kt index f24eccd8ba..049edd5b83 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/steps/Step.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/steps/Step.kt @@ -121,6 +121,14 @@ abstract class StepResultMixin : StepResult ) abstract class StepParamsMixin : StepParams +/** + * Step contains all of the information required to execute an orchestration step and the result of the execution. + * + * All classes used in the params structure must implement [StepParams] interface and added to [StepParamsMixin]. + * All classes used in the result structure must implement [StepResult] interface and added to [StepResultMixin]. + * + * Additionally, [StepParams] and [StepResult] subclasses can only have fields of primitives, enums and serializeables classes. + */ @Keep internal data class Step( val id: Int, diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/cache/OrchestratorCacheIntegrationTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/cache/OrchestratorCacheIntegrationTest.kt index ab32d72657..ca28e48fa2 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/cache/OrchestratorCacheIntegrationTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/cache/OrchestratorCacheIntegrationTest.kt @@ -1,23 +1,57 @@ package com.simprints.feature.orchestrator.cache import android.content.SharedPreferences -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.common.truth.Truth.assertThat +import androidx.test.ext.junit.runners.* +import com.google.common.truth.Truth.* +import com.simprints.core.domain.common.FlowType import com.simprints.core.domain.fingerprint.IFingerIdentifier +import com.simprints.core.domain.response.AppErrorReason +import com.simprints.core.domain.tokenization.TokenizableString import com.simprints.core.tools.json.JsonHelper import com.simprints.face.capture.FaceCaptureParams +import com.simprints.face.capture.FaceCaptureResult +import com.simprints.feature.alert.AlertResult +import com.simprints.feature.consent.ConsentParams +import com.simprints.feature.consent.ConsentResult +import com.simprints.feature.consent.ConsentType +import com.simprints.feature.enrollast.EnrolLastBiometricParams +import com.simprints.feature.enrollast.EnrolLastBiometricStepResult +import com.simprints.feature.enrollast.FaceTemplateCaptureResult +import com.simprints.feature.enrollast.FingerTemplateCaptureResult +import com.simprints.feature.enrollast.MatchResult +import com.simprints.feature.exitform.ExitFormOption +import com.simprints.feature.exitform.ExitFormResult +import com.simprints.feature.fetchsubject.FetchSubjectParams +import com.simprints.feature.fetchsubject.FetchSubjectResult +import com.simprints.feature.login.LoginError +import com.simprints.feature.login.LoginParams +import com.simprints.feature.login.LoginResult import com.simprints.feature.orchestrator.steps.Step import com.simprints.feature.orchestrator.steps.StepId import com.simprints.feature.orchestrator.steps.StepStatus +import com.simprints.feature.selectagegroup.SelectSubjectAgeGroupResult +import com.simprints.feature.selectsubject.SelectSubjectParams +import com.simprints.feature.selectsubject.SelectSubjectResult +import com.simprints.feature.setup.SetupResult +import com.simprints.feature.validatepool.ValidateSubjectPoolFragmentParams +import com.simprints.feature.validatepool.ValidateSubjectPoolResult +import com.simprints.fingerprint.capture.FingerprintCaptureParams import com.simprints.fingerprint.capture.FingerprintCaptureResult +import com.simprints.infra.config.store.models.AgeGroup import com.simprints.infra.config.store.models.FaceConfiguration +import com.simprints.infra.config.store.models.Finger +import com.simprints.infra.config.store.models.FingerprintConfiguration +import com.simprints.infra.enrolment.records.repository.domain.models.BiometricDataSource +import com.simprints.infra.enrolment.records.repository.domain.models.SubjectQuery import com.simprints.infra.events.sampledata.SampleDefaults.GUID1 +import com.simprints.infra.images.model.Path +import com.simprints.infra.images.model.SecuredImageRef import com.simprints.infra.security.SecurityManager -import io.mockk.MockKAnnotations -import io.mockk.every +import com.simprints.matcher.FaceMatchResult +import com.simprints.matcher.FingerprintMatchResult +import com.simprints.matcher.MatchParams +import io.mockk.* import io.mockk.impl.annotations.MockK -import io.mockk.justRun -import io.mockk.slot import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -52,12 +86,119 @@ class OrchestratorCacheIntegrationTest { } @Test - fun `Stores and restores steps`() { + fun `Stores and restores common steps`() { + val expected = listOf( + Step( + id = StepId.SETUP, + navigationActionId = 5, + destinationId = 6, + params = null, + status = StepStatus.IN_PROGRESS, + result = SetupResult(true), + ), + Step( + id = StepId.FETCH_GUID, + navigationActionId = 5, + destinationId = 6, + params = FetchSubjectParams("projectId", "subjectId", ""), + status = StepStatus.COMPLETED, + result = FetchSubjectResult(false), + ), + Step( + id = StepId.CONSENT, + navigationActionId = 5, + destinationId = 6, + params = ConsentParams(type = ConsentType.ENROL), + status = StepStatus.COMPLETED, + result = ConsentResult(true), + ), + Step( + id = StepId.ENROL_LAST_BIOMETRIC, + navigationActionId = 5, + destinationId = 6, + params = EnrolLastBiometricParams( + projectId = "projectId", + userId = TokenizableString.Raw("value"), + moduleId = TokenizableString.Raw("value"), + steps = listOf( + EnrolLastBiometricStepResult.FingerprintCaptureResult( + "referenceId", + listOf( + FingerTemplateCaptureResult( + Finger.LEFT_4TH_FINGER, + byteArrayOf(1, 2, 3), + 10, + "NEC", + ), + ), + ), + EnrolLastBiometricStepResult.FingerprintMatchResult( + listOf(MatchResult("subjectId", 0.5f)), + FingerprintConfiguration.BioSdk.SECUGEN_SIM_MATCHER, + ), + EnrolLastBiometricStepResult.FaceCaptureResult( + "referenceId", + listOf( + FaceTemplateCaptureResult(byteArrayOf(1, 2, 3), "RankOne"), + ), + ), + EnrolLastBiometricStepResult.FaceMatchResult( + listOf(MatchResult("subjectId", 0.5f)), + FaceConfiguration.BioSdk.RANK_ONE, + ), + EnrolLastBiometricStepResult.EnrolLastBiometricsResult("subjectId"), + ), + ), + status = StepStatus.COMPLETED, + result = ValidateSubjectPoolResult(true), + ), + Step( + id = StepId.CONFIRM_IDENTITY, + navigationActionId = 5, + destinationId = 6, + params = SelectSubjectParams("projectId", "subjectId"), + status = StepStatus.COMPLETED, + result = SelectSubjectResult(true), + ), + Step( + id = StepId.VALIDATE_ID_POOL, + navigationActionId = 5, + destinationId = 6, + params = ValidateSubjectPoolFragmentParams(SubjectQuery()), + status = StepStatus.COMPLETED, + result = ValidateSubjectPoolResult(true), + ), + Step( + id = StepId.SELECT_SUBJECT_AGE, + navigationActionId = 5, + destinationId = 6, + params = null, + status = StepStatus.COMPLETED, + result = SelectSubjectAgeGroupResult(AgeGroup(10, null)), + ), + ) + + cache.steps = expected + val actual = cache.steps + + assertThat(actual).hasSize(expected.size) + for (i in expected.indices) { + compareStubs(expected[i], actual[i]) + } + } + + @Test + fun `Stores and restores fingerprint modality steps`() { val expected = listOf( Step( id = StepId.FINGERPRINT_CAPTURE, navigationActionId = 3, destinationId = 4, + params = FingerprintCaptureParams( + flowType = FlowType.ENROL, + fingerprintsToCapture = listOf(IFingerIdentifier.LEFT_4TH_FINGER), + fingerprintSDK = FingerprintConfiguration.BioSdk.SECUGEN_SIM_MATCHER, + ), status = StepStatus.COMPLETED, result = FingerprintCaptureResult( "", @@ -65,27 +206,143 @@ class OrchestratorCacheIntegrationTest { FingerprintCaptureResult.Item( captureEventId = GUID1, identifier = IFingerIdentifier.LEFT_THUMB, - sample = null, + sample = FingerprintCaptureResult.Sample( + fingerIdentifier = IFingerIdentifier.LEFT_4TH_FINGER, + template = byteArrayOf(1, 2, 3), + templateQualityScore = 10, + imageRef = SecuredImageRef(Path("file/path")), + format = "NEC", + ), ), ), ), ), + Step( + id = StepId.FINGERPRINT_MATCHER, + navigationActionId = 3, + destinationId = 4, + params = MatchParams( + probeReferenceId = GUID1, + flowType = FlowType.IDENTIFY, + queryForCandidates = SubjectQuery(), + biometricDataSource = BiometricDataSource.CommCare("name"), + probeFingerprintSamples = listOf( + MatchParams.FingerprintSample( + fingerId = IFingerIdentifier.LEFT_4TH_FINGER, + format = "NEC", + template = byteArrayOf(1, 2, 3), + ), + ), + ), + status = StepStatus.COMPLETED, + result = FingerprintMatchResult( + listOf(FingerprintMatchResult.Item("subjectId", 0.5f)), + FingerprintConfiguration.BioSdk.SECUGEN_SIM_MATCHER, + ), + ), + ) + + cache.steps = expected + val actual = cache.steps + + assertThat(actual).hasSize(expected.size) + for (i in expected.indices) { + compareStubs(expected[i], actual[i]) + } + } + + @Test + fun `Stores and restores face modality steps`() { + val expected = listOf( Step( id = StepId.FACE_CAPTURE, navigationActionId = 5, destinationId = 6, params = FaceCaptureParams(3, FaceConfiguration.BioSdk.RANK_ONE), status = StepStatus.COMPLETED, - result = null, + result = FaceCaptureResult( + "", + results = listOf( + FaceCaptureResult.Item( + captureEventId = "event", + index = 0, + sample = FaceCaptureResult.Sample( + faceId = "faceId", + template = byteArrayOf(1, 2, 3), + imageRef = SecuredImageRef(Path("file/path")), + format = "ROC", + ), + ), + ), + ), + ), + Step( + id = StepId.FACE_MATCHER, + navigationActionId = 3, + destinationId = 4, + params = MatchParams( + probeReferenceId = GUID1, + flowType = FlowType.IDENTIFY, + queryForCandidates = SubjectQuery(), + biometricDataSource = BiometricDataSource.Simprints, + probeFaceSamples = listOf( + MatchParams.FaceSample( + faceId = "faceId", + template = byteArrayOf(1, 2, 3), + ), + ), + ), + status = StepStatus.COMPLETED, + result = FaceMatchResult( + listOf(FaceMatchResult.Item("subjectId", 0.5f)), + FaceConfiguration.BioSdk.RANK_ONE, + ), + ), + ) + + cache.steps = expected + val actual = cache.steps + + assertThat(actual).hasSize(expected.size) + for (i in expected.indices) { + compareStubs(expected[i], actual[i]) + } + } + + @Test + fun `Stores and restores exception steps`() { + val expected = listOf( + Step( + id = 1, + navigationActionId = 5, + destinationId = 6, + params = LoginParams("projectId", TokenizableString.Tokenized("value")), + status = StepStatus.NOT_STARTED, + result = LoginResult(false, LoginError.LoginNotCompleted), + ), + Step( + id = 2, + navigationActionId = 5, + destinationId = 6, + status = StepStatus.NOT_STARTED, + result = AlertResult("key", AppErrorReason.UNEXPECTED_ERROR), + ), + Step( + id = 3, + navigationActionId = 5, + destinationId = 6, + status = StepStatus.NOT_STARTED, + result = ExitFormResult(true, ExitFormOption.DataConcerns), ), ) cache.steps = expected val actual = cache.steps - assertThat(actual).hasSize(2) - compareStubs(expected[0], actual[0]) - compareStubs(expected[1], actual[1]) + assertThat(actual).hasSize(expected.size) + for (i in expected.indices) { + compareStubs(expected[i], actual[i]) + } } private fun compareStubs( diff --git a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/FingerprintCaptureResult.kt b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/FingerprintCaptureResult.kt index ad73c56792..a8c8b916ef 100644 --- a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/FingerprintCaptureResult.kt +++ b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/FingerprintCaptureResult.kt @@ -1,6 +1,7 @@ package com.simprints.fingerprint.capture import androidx.annotation.Keep +import com.simprints.core.ExcludedFromGeneratedTestCoverageReports import com.simprints.core.domain.fingerprint.IFingerIdentifier import com.simprints.core.domain.step.StepResult import com.simprints.infra.images.model.SecuredImageRef @@ -24,5 +25,31 @@ data class FingerprintCaptureResult( val templateQualityScore: Int, val imageRef: SecuredImageRef?, val format: String, - ) : StepResult + ) : StepResult { + @ExcludedFromGeneratedTestCoverageReports("Generated code") + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Sample + + if (templateQualityScore != other.templateQualityScore) return false + if (fingerIdentifier != other.fingerIdentifier) return false + if (!template.contentEquals(other.template)) return false + if (imageRef != other.imageRef) return false + if (format != other.format) return false + + return true + } + + @ExcludedFromGeneratedTestCoverageReports("Generated code") + override fun hashCode(): Int { + var result = templateQualityScore + result = 31 * result + fingerIdentifier.hashCode() + result = 31 * result + template.contentHashCode() + result = 31 * result + (imageRef?.hashCode() ?: 0) + result = 31 * result + format.hashCode() + return result + } + } } diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/BiometricDataSource.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/BiometricDataSource.kt index d468c0e756..6b69d6abb3 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/BiometricDataSource.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/BiometricDataSource.kt @@ -13,7 +13,7 @@ sealed class BiometricDataSource : StepParams { data object Simprints : BiometricDataSource() data class CommCare( - private val callerPackageName: String, + val callerPackageName: String, ) : BiometricDataSource() { override fun callerPackageName() = callerPackageName From d5f0467dc3979c22d76d322d2f1b44fa57180567 Mon Sep 17 00:00:00 2001 From: Melad Raouf Date: Thu, 23 Oct 2025 13:23:41 +0100 Subject: [PATCH 4/6] Update OrchestratorCacheIntegrationTest to use new match result classes and handle null parameters --- .../cache/OrchestratorCacheIntegrationTest.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/cache/OrchestratorCacheIntegrationTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/cache/OrchestratorCacheIntegrationTest.kt index ca28e48fa2..c2c6482aa6 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/cache/OrchestratorCacheIntegrationTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/cache/OrchestratorCacheIntegrationTest.kt @@ -46,10 +46,10 @@ import com.simprints.infra.enrolment.records.repository.domain.models.SubjectQue import com.simprints.infra.events.sampledata.SampleDefaults.GUID1 import com.simprints.infra.images.model.Path import com.simprints.infra.images.model.SecuredImageRef +import com.simprints.infra.matching.FaceMatchResult +import com.simprints.infra.matching.FingerprintMatchResult +import com.simprints.infra.matching.MatchParams import com.simprints.infra.security.SecurityManager -import com.simprints.matcher.FaceMatchResult -import com.simprints.matcher.FingerprintMatchResult -import com.simprints.matcher.MatchParams import io.mockk.* import io.mockk.impl.annotations.MockK import org.junit.Before @@ -148,6 +148,7 @@ class OrchestratorCacheIntegrationTest { ), EnrolLastBiometricStepResult.EnrolLastBiometricsResult("subjectId"), ), + scannedCredential = null, ), status = StepStatus.COMPLETED, result = ValidateSubjectPoolResult(true), @@ -156,9 +157,9 @@ class OrchestratorCacheIntegrationTest { id = StepId.CONFIRM_IDENTITY, navigationActionId = 5, destinationId = 6, - params = SelectSubjectParams("projectId", "subjectId"), + params = SelectSubjectParams("projectId", "subjectId", null), status = StepStatus.COMPLETED, - result = SelectSubjectResult(true), + result = SelectSubjectResult(true, savedCredential = null), ), Step( id = StepId.VALIDATE_ID_POOL, From ec0523fdb5ea5590fbc14af4f8158429db757f63 Mon Sep 17 00:00:00 2001 From: Melad Raouf Date: Thu, 23 Oct 2025 13:42:13 +0100 Subject: [PATCH 5/6] Update version to 2025.4.0 --- build-logic/build_properties.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-logic/build_properties.gradle.kts b/build-logic/build_properties.gradle.kts index 1f50152ac9..e65867a496 100644 --- a/build-logic/build_properties.gradle.kts +++ b/build-logic/build_properties.gradle.kts @@ -19,7 +19,7 @@ extra.apply { * Dev version >= 2025.2.0 is required to support enrolment record updates and SimFace configuration * Dev version >= 2025.3.0 is required to receive smaples and structured down sync configuration */ - set("VERSION_NAME", "2025.3.0") + set("VERSION_NAME", "2025.4.0") /** * Build type. The version code describes which build type was used for the build. From 6b7cad1c965114de5236cc0cb3f44c9c7c1ab8d5 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Thu, 23 Oct 2025 15:59:55 +0300 Subject: [PATCH 6/6] Add MFID step with params and results to orchestrator cache integration test --- .../model/CredentialMatch.kt | 3 +- .../screens/search/model/ScannedCredential.kt | 3 +- .../cache/OrchestratorCacheIntegrationTest.kt | 57 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/model/CredentialMatch.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/model/CredentialMatch.kt index a628729173..3fd619dac9 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/model/CredentialMatch.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/model/CredentialMatch.kt @@ -2,6 +2,7 @@ package com.simprints.feature.externalcredential.model import androidx.annotation.Keep import com.simprints.core.ExcludedFromGeneratedTestCoverageReports +import com.simprints.core.domain.step.StepResult import com.simprints.core.domain.tokenization.TokenizableString import com.simprints.infra.config.store.models.FaceConfiguration import com.simprints.infra.config.store.models.FingerprintConfiguration @@ -15,6 +16,6 @@ data class CredentialMatch( val verificationThreshold: Float, val faceBioSdk: FaceConfiguration.BioSdk?, val fingerprintBioSdk: FingerprintConfiguration.BioSdk?, -) { +) : StepResult { val isVerificationSuccessful = matchResult.confidence >= verificationThreshold } diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/search/model/ScannedCredential.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/search/model/ScannedCredential.kt index 134340f857..bf2c269b61 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/search/model/ScannedCredential.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/search/model/ScannedCredential.kt @@ -3,6 +3,7 @@ package com.simprints.feature.externalcredential.screens.search.model import androidx.annotation.Keep import com.simprints.core.domain.externalcredential.ExternalCredential import com.simprints.core.domain.externalcredential.ExternalCredentialType +import com.simprints.core.domain.step.StepResult import com.simprints.core.domain.tokenization.TokenizableString import com.simprints.core.tools.time.Timestamp import com.simprints.core.tools.utils.randomUUID @@ -20,7 +21,7 @@ data class ScannedCredential( val scanStartTime: Timestamp, val scanEndTime: Timestamp, val scannedValue: TokenizableString.Raw, -) : Serializable +) : StepResult fun ScannedCredential.toExternalCredential(subjectId: String) = ExternalCredential( id = credentialScanId, diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/cache/OrchestratorCacheIntegrationTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/cache/OrchestratorCacheIntegrationTest.kt index c2c6482aa6..e450f188d0 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/cache/OrchestratorCacheIntegrationTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/cache/OrchestratorCacheIntegrationTest.kt @@ -4,10 +4,14 @@ import android.content.SharedPreferences import androidx.test.ext.junit.runners.* import com.google.common.truth.Truth.* import com.simprints.core.domain.common.FlowType +import com.simprints.core.domain.externalcredential.ExternalCredentialType import com.simprints.core.domain.fingerprint.IFingerIdentifier import com.simprints.core.domain.response.AppErrorReason import com.simprints.core.domain.tokenization.TokenizableString +import com.simprints.core.domain.tokenization.asTokenizableEncrypted +import com.simprints.core.domain.tokenization.asTokenizableRaw import com.simprints.core.tools.json.JsonHelper +import com.simprints.core.tools.time.Timestamp import com.simprints.face.capture.FaceCaptureParams import com.simprints.face.capture.FaceCaptureResult import com.simprints.feature.alert.AlertResult @@ -21,6 +25,11 @@ import com.simprints.feature.enrollast.FingerTemplateCaptureResult import com.simprints.feature.enrollast.MatchResult import com.simprints.feature.exitform.ExitFormOption import com.simprints.feature.exitform.ExitFormResult +import com.simprints.feature.externalcredential.ExternalCredentialSearchResult +import com.simprints.feature.externalcredential.model.BoundingBox +import com.simprints.feature.externalcredential.model.CredentialMatch +import com.simprints.feature.externalcredential.model.ExternalCredentialParams +import com.simprints.feature.externalcredential.screens.search.model.ScannedCredential import com.simprints.feature.fetchsubject.FetchSubjectParams import com.simprints.feature.fetchsubject.FetchSubjectResult import com.simprints.feature.login.LoginError @@ -177,6 +186,54 @@ class OrchestratorCacheIntegrationTest { status = StepStatus.COMPLETED, result = SelectSubjectAgeGroupResult(AgeGroup(10, null)), ), + Step( + id = StepId.EXTERNAL_CREDENTIAL, + navigationActionId = 5, + destinationId = 6, + params = ExternalCredentialParams( + subjectId = "subjectId", + flowType = FlowType.IDENTIFY, + ageGroup = AgeGroup(1, 2), + probeReferenceId = "referenceId", + faceSamples = listOf( + MatchParams.FaceSample( + faceId = "faceId", + template = byteArrayOf(1, 2, 3), + ), + ), + fingerprintSamples = listOf( + MatchParams.FingerprintSample( + fingerId = IFingerIdentifier.LEFT_4TH_FINGER, + format = "NEC", + template = byteArrayOf(1, 2, 3), + ), + ), + ), + status = StepStatus.COMPLETED, + result = ExternalCredentialSearchResult( + flowType = FlowType.IDENTIFY, + scannedCredential = ScannedCredential( + credentialScanId = "scanId", + credential = "credential".asTokenizableEncrypted(), + credentialType = ExternalCredentialType.GhanaIdCard, + documentImagePath = "image/path.jpg", + zoomedCredentialImagePath = "image/path.jpg", + credentialBoundingBox = BoundingBox(0, 1, 2, 3), + scanStartTime = Timestamp(1L), + scanEndTime = Timestamp(2L, false, 123L), + scannedValue = "credential".asTokenizableRaw(), + ), + matchResults = listOf( + CredentialMatch( + credential = "credential".asTokenizableEncrypted(), + matchResult = FaceMatchResult.Item("subjectId", 0.5f), + verificationThreshold = 55f, + faceBioSdk = FaceConfiguration.BioSdk.RANK_ONE, + fingerprintBioSdk = FingerprintConfiguration.BioSdk.SECUGEN_SIM_MATCHER, + ), + ), + ), + ), ) cache.steps = expected