From 4f6f3efc95bd1c907426149af283436237dc7a98 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 5 Feb 2025 17:42:56 +0200 Subject: [PATCH 01/22] MS-871 Add BiometricReferenceCreationEvent with boilerplate --- .../ApiBiometricReferenceCreationPayload.kt | 22 +++++++ .../event/remote/models/ApiEventPayload.kt | 10 ++-- .../remote/models/ApiEventPayloadType.kt | 7 +++ .../eventsync/event/EventValidationUtils.kt | 10 ++++ ...piBiometricReferenceCreationPayloadTest.kt | 16 ++++++ .../MapDomainEventToApiUseCaseTest.kt | 13 +++++ .../testtools/RemoteTestingHelper.kt | 18 +++--- .../events/sampledata/EventFactoryUtils.kt | 8 +++ .../models/BiometricReferenceCreationEvent.kt | 57 +++++++++++++++++++ .../infra/events/event/domain/models/Event.kt | 2 + .../event/domain/models/EventPayload.kt | 2 + .../events/event/domain/models/EventType.kt | 4 ++ .../BiometricReferenceCreationEventTest.kt | 34 +++++++++++ .../biometric_reference_creation_v1.json | 21 +++++++ 14 files changed, 213 insertions(+), 11 deletions(-) create mode 100644 infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiBiometricReferenceCreationPayload.kt create mode 100644 infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiBiometricReferenceCreationPayloadTest.kt create mode 100644 infra/events/src/main/java/com/simprints/infra/events/event/domain/models/BiometricReferenceCreationEvent.kt create mode 100644 infra/events/src/test/java/com/simprints/infra/events/event/domain/models/BiometricReferenceCreationEventTest.kt create mode 100644 infra/events/src/test/resources/all-events/biometric_reference_creation_v1.json diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiBiometricReferenceCreationPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiBiometricReferenceCreationPayload.kt new file mode 100644 index 0000000000..770e9d891a --- /dev/null +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiBiometricReferenceCreationPayload.kt @@ -0,0 +1,22 @@ +package com.simprints.infra.eventsync.event.remote.models + +import androidx.annotation.Keep +import com.simprints.infra.config.store.models.TokenKeyType +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent.BiometricReferenceCreationPayload + +@Keep +internal data class ApiBiometricReferenceCreationPayload( + override val startTime: ApiTimestamp, + val id: String, + val modality: String, + val captureIds: List, +) : ApiEventPayload(startTime) { + constructor(domainPayload: BiometricReferenceCreationPayload) : this( + domainPayload.createdAt.fromDomainToApi(), + domainPayload.id, + domainPayload.modality.name, + domainPayload.captureIds, + ) + + override fun getTokenizedFieldJsonPath(tokenKeyType: TokenKeyType): String? = null +} diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt index 34c0114514..3ef4f323f7 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt @@ -8,6 +8,7 @@ import com.simprints.infra.events.event.domain.models.AgeGroupSelectionEvent import com.simprints.infra.events.event.domain.models.AlertScreenEvent.AlertScreenPayload import com.simprints.infra.events.event.domain.models.AuthenticationEvent.AuthenticationPayload import com.simprints.infra.events.event.domain.models.AuthorizationEvent.AuthorizationPayload +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent.BiometricReferenceCreationPayload import com.simprints.infra.events.event.domain.models.CandidateReadEvent.CandidateReadPayload import com.simprints.infra.events.event.domain.models.CompletionCheckEvent.CompletionCheckPayload import com.simprints.infra.events.event.domain.models.ConnectivitySnapshotEvent.ConnectivitySnapshotPayload @@ -19,6 +20,7 @@ import com.simprints.infra.events.event.domain.models.EventType.AGE_GROUP_SELECT import com.simprints.infra.events.event.domain.models.EventType.ALERT_SCREEN import com.simprints.infra.events.event.domain.models.EventType.AUTHENTICATION import com.simprints.infra.events.event.domain.models.EventType.AUTHORIZATION +import com.simprints.infra.events.event.domain.models.EventType.BIOMETRIC_REFERENCE_CREATION import com.simprints.infra.events.event.domain.models.EventType.CALLBACK_CONFIRMATION import com.simprints.infra.events.event.domain.models.EventType.CALLBACK_ENROLMENT import com.simprints.infra.events.event.domain.models.EventType.CALLBACK_ERROR @@ -86,7 +88,7 @@ import com.simprints.infra.events.event.domain.models.face.FaceCaptureConfirmati import com.simprints.infra.events.event.domain.models.face.FaceCaptureEvent import com.simprints.infra.events.event.domain.models.face.FaceFallbackCaptureEvent.FaceFallbackCapturePayload import com.simprints.infra.events.event.domain.models.face.FaceOnboardingCompleteEvent.FaceOnboardingCompletePayload -import com.simprints.infra.events.event.domain.models.fingerprint.FingerprintCaptureBiometricsEvent +import com.simprints.infra.events.event.domain.models.fingerprint.FingerprintCaptureBiometricsEvent.FingerprintCaptureBiometricsPayload import com.simprints.infra.events.event.domain.models.fingerprint.FingerprintCaptureEvent import com.simprints.infra.events.event.domain.models.upsync.EventUpSyncRequestEvent import com.simprints.infra.eventsync.event.remote.models.ApiEventPayloadType.Companion @@ -132,6 +134,7 @@ import com.simprints.infra.eventsync.event.remote.models.upsync.ApiEventUpSyncRe JsonSubTypes.Type(value = ApiCalloutPayload::class, name = Companion.CALLBACK_KEY), JsonSubTypes.Type(value = ApiEventDownSyncRequestPayload::class, name = Companion.EVENT_DOWN_SYNC_REQUEST_KEY), JsonSubTypes.Type(value = ApiEventUpSyncRequestPayload::class, name = Companion.EVENT_UP_SYNC_REQUEST_KEY), + JsonSubTypes.Type(value = ApiBiometricReferenceCreationPayload::class, name = Companion.BIOMETRIC_REFERENCE_CREATION_KEY), ) @Keep internal abstract class ApiEventPayload( @@ -177,12 +180,11 @@ internal fun EventPayload.fromDomainToApi(): ApiEventPayload = when (this.type) CALLBACK_VERIFICATION -> ApiCallbackPayload(this as VerificationCallbackPayload) CALLBACK_ERROR -> ApiCallbackPayload(this as ErrorCallbackPayload) CALLBACK_CONFIRMATION -> ApiCallbackPayload(this as ConfirmationCallbackPayload) - FINGERPRINT_CAPTURE_BIOMETRICS -> ApiFingerprintCaptureBiometricsPayload( - this as FingerprintCaptureBiometricsEvent.FingerprintCaptureBiometricsPayload, - ) + FINGERPRINT_CAPTURE_BIOMETRICS -> ApiFingerprintCaptureBiometricsPayload(this as FingerprintCaptureBiometricsPayload) FACE_CAPTURE_BIOMETRICS -> ApiFaceCaptureBiometricsPayload(this as FaceCaptureBiometricsEvent.FaceCaptureBiometricsPayload) EVENT_DOWN_SYNC_REQUEST -> ApiEventDownSyncRequestPayload(this as EventDownSyncRequestEvent.EventDownSyncRequestPayload) EVENT_UP_SYNC_REQUEST -> ApiEventUpSyncRequestPayload(this as EventUpSyncRequestEvent.EventUpSyncRequestPayload) LICENSE_CHECK -> ApiLicenseCheckEventPayload(this as LicenseCheckEvent.LicenseCheckEventPayload) AGE_GROUP_SELECTION -> ApiAgeGroupSelectionPayload(this as AgeGroupSelectionEvent.AgeGroupSelectionPayload) + BIOMETRIC_REFERENCE_CREATION -> ApiBiometricReferenceCreationPayload(this as BiometricReferenceCreationPayload) } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayloadType.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayloadType.kt index 82602e8129..72ba14267c 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayloadType.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayloadType.kt @@ -6,6 +6,7 @@ import com.simprints.infra.events.event.domain.models.EventType.AGE_GROUP_SELECT import com.simprints.infra.events.event.domain.models.EventType.ALERT_SCREEN import com.simprints.infra.events.event.domain.models.EventType.AUTHENTICATION import com.simprints.infra.events.event.domain.models.EventType.AUTHORIZATION +import com.simprints.infra.events.event.domain.models.EventType.BIOMETRIC_REFERENCE_CREATION import com.simprints.infra.events.event.domain.models.EventType.CALLBACK_CONFIRMATION import com.simprints.infra.events.event.domain.models.EventType.CALLBACK_ENROLMENT import com.simprints.infra.events.event.domain.models.EventType.CALLBACK_ERROR @@ -147,6 +148,9 @@ internal enum class ApiEventPayloadType { // key added: AGE_GROUP_SELECTION_KEY AgeGroupSelection, + // key added: BIOMETRIC_REFERENCE_CREATION_KEY + BiometricReferenceCreation, + ; companion object { @@ -180,6 +184,7 @@ internal enum class ApiEventPayloadType { const val FINGERPRINT_CAPTURE_BIOMETRICS_KEY = "FingerprintCaptureBiometrics" const val EVENT_DOWN_SYNC_REQUEST_KEY = "EventDownSyncRequest" const val EVENT_UP_SYNC_REQUEST_KEY = "EventUpSyncRequest" + const val BIOMETRIC_REFERENCE_CREATION_KEY = "BiometricReferenceCreation" } } @@ -229,6 +234,7 @@ internal fun EventType.fromDomainToApi(): ApiEventPayloadType = when (this) { EVENT_UP_SYNC_REQUEST -> ApiEventPayloadType.EventUpSyncRequest LICENSE_CHECK -> ApiEventPayloadType.LicenseCheck AGE_GROUP_SELECTION -> ApiEventPayloadType.AgeGroupSelection + BIOMETRIC_REFERENCE_CREATION -> ApiEventPayloadType.BiometricReferenceCreation } internal fun ApiEventPayloadType.fromApiToDomain(): EventType = when (this) { @@ -262,6 +268,7 @@ internal fun ApiEventPayloadType.fromApiToDomain(): EventType = when (this) { ApiEventPayloadType.EventUpSyncRequest -> EVENT_UP_SYNC_REQUEST ApiEventPayloadType.LicenseCheck -> LICENSE_CHECK ApiEventPayloadType.AgeGroupSelection -> AGE_GROUP_SELECTION + ApiEventPayloadType.BiometricReferenceCreation -> BIOMETRIC_REFERENCE_CREATION ApiEventPayloadType.Callout -> throw UnsupportedOperationException("") ApiEventPayloadType.Callback -> throw UnsupportedOperationException("") } diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt index dc73c21969..a7256f943e 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt @@ -631,4 +631,14 @@ fun validateAgeGroupSelectionEventApiModel(json: JSONObject) { } } +fun validateBiometricReferenceCreationEventApiModel(json: JSONObject) { + validateCommonParams(json, "BiometricReferenceCreation", 1) + with(json.getJSONObject("payload")) { + validateTimestamp(getJSONObject("startTime")) + assertThat(getString("id")).isNotNull() + assertThat(getString("modality")).isNotNull() + assertThat(getString("captureIds")).isNotNull() + } +} + private fun Array.valuesAsStrings(): List = this.map { it.toString() } diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiBiometricReferenceCreationPayloadTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiBiometricReferenceCreationPayloadTest.kt new file mode 100644 index 0000000000..e0dbe019cd --- /dev/null +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiBiometricReferenceCreationPayloadTest.kt @@ -0,0 +1,16 @@ +package com.simprints.infra.eventsync.event.remote.models + +import com.google.common.truth.Truth +import com.simprints.infra.config.store.models.TokenKeyType +import io.mockk.mockk +import org.junit.Test + +class ApiBiometricReferenceCreationPayloadTest { + @Test + fun `when getTokenizedFieldJsonPath is invoked, null is returned`() { + val payload = ApiBiometricReferenceCreationPayload(domainPayload = mockk(relaxed = true)) + TokenKeyType.entries.forEach { + Truth.assertThat(payload.getTokenizedFieldJsonPath(it)).isNull() + } + } +} diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventToApiUseCaseTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventToApiUseCaseTest.kt index 61a8a11156..d8da5c05ab 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventToApiUseCaseTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventToApiUseCaseTest.kt @@ -15,6 +15,7 @@ import com.simprints.infra.events.sampledata.createAgeGroupSelectionEvent import com.simprints.infra.events.sampledata.createAlertScreenEvent import com.simprints.infra.events.sampledata.createAuthenticationEvent import com.simprints.infra.events.sampledata.createAuthorizationEvent +import com.simprints.infra.events.sampledata.createBiometricReferenceCreationEvent import com.simprints.infra.events.sampledata.createCandidateReadEvent import com.simprints.infra.events.sampledata.createCompletionCheckEvent import com.simprints.infra.events.sampledata.createConfirmationCallbackEvent @@ -58,6 +59,7 @@ import com.simprints.infra.eventsync.event.remote.models.ApiEventPayloadType.Age import com.simprints.infra.eventsync.event.remote.models.ApiEventPayloadType.AlertScreen import com.simprints.infra.eventsync.event.remote.models.ApiEventPayloadType.Authentication import com.simprints.infra.eventsync.event.remote.models.ApiEventPayloadType.Authorization +import com.simprints.infra.eventsync.event.remote.models.ApiEventPayloadType.BiometricReferenceCreation import com.simprints.infra.eventsync.event.remote.models.ApiEventPayloadType.Callback import com.simprints.infra.eventsync.event.remote.models.ApiEventPayloadType.Callout import com.simprints.infra.eventsync.event.remote.models.ApiEventPayloadType.CandidateRead @@ -90,6 +92,7 @@ import com.simprints.infra.eventsync.event.validateAgeGroupSelectionEventApiMode import com.simprints.infra.eventsync.event.validateAlertScreenEventApiModel import com.simprints.infra.eventsync.event.validateAuthenticationEventApiModel import com.simprints.infra.eventsync.event.validateAuthorizationEventApiModel +import com.simprints.infra.eventsync.event.validateBiometricReferenceCreationEventApiModel import com.simprints.infra.eventsync.event.validateCallbackV1EventApiModel import com.simprints.infra.eventsync.event.validateCallbackV2EventApiModel import com.simprints.infra.eventsync.event.validateCalloutEventApiModel @@ -521,6 +524,15 @@ internal class MapDomainEventToApiUseCaseTest { validateAgeGroupSelectionEventApiModel(json) } + @Test + fun validate_biometricReferenceCreationEventApiModel() { + val event = createBiometricReferenceCreationEvent() + val apiEvent = event.fromDomainToApi() + val json = JSONObject(jackson.writeValueAsString(apiEvent)) + + validateBiometricReferenceCreationEventApiModel(json) + } + @Test fun `when event contains tokenized attendant id, then ApiEvent should contain tokenizedField`() { validateUserIdTokenization(attendantId = "attendantId".asTokenizableEncrypted()) @@ -621,6 +633,7 @@ internal class MapDomainEventToApiUseCaseTest { EventUpSyncRequest -> validate_UpSyncRequestEventApiModel() LicenseCheck -> validate_licenseCheckEventApiModel() AgeGroupSelection -> validate_ageGroupSelectionEventApiModel() + BiometricReferenceCreation -> validate_biometricReferenceCreationEventApiModel() null -> TODO() }.safeSealedWhens } diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/testtools/RemoteTestingHelper.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/testtools/RemoteTestingHelper.kt index a1a3659ba8..c5a24cf748 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/testtools/RemoteTestingHelper.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/testtools/RemoteTestingHelper.kt @@ -7,13 +7,17 @@ internal class RemoteTestingHelper { fun enforceThatAnyTestHasATest(type: ApiEventPayloadType?) { when (type) { ApiEventPayloadType.Callout, ApiEventPayloadType.Callback, - ApiEventPayloadType.Authentication, ApiEventPayloadType.Consent, ApiEventPayloadType.Enrolment, ApiEventPayloadType.Authorization, ApiEventPayloadType.FingerprintCapture, ApiEventPayloadType.OneToOneMatch, - ApiEventPayloadType.OneToManyMatch, ApiEventPayloadType.PersonCreation, ApiEventPayloadType.AlertScreen, ApiEventPayloadType.GuidSelection, ApiEventPayloadType.ConnectivitySnapshot, ApiEventPayloadType.Refusal, ApiEventPayloadType.CandidateRead, - ApiEventPayloadType.ScannerConnection, ApiEventPayloadType.Vero2InfoSnapshot, ApiEventPayloadType.ScannerFirmwareUpdate, ApiEventPayloadType.InvalidIntent, ApiEventPayloadType.SuspiciousIntent, ApiEventPayloadType.IntentParsing, - ApiEventPayloadType.CompletionCheck, ApiEventPayloadType.FaceOnboardingComplete, ApiEventPayloadType.FaceFallbackCapture, ApiEventPayloadType.FaceCapture, - ApiEventPayloadType.FaceCaptureConfirmation, ApiEventPayloadType.FingerprintCaptureBiometrics, ApiEventPayloadType.FaceCaptureBiometrics, - ApiEventPayloadType.EventDownSyncRequest, ApiEventPayloadType.EventUpSyncRequest, - ApiEventPayloadType.LicenseCheck, ApiEventPayloadType.AgeGroupSelection, + ApiEventPayloadType.Authentication, ApiEventPayloadType.Consent, ApiEventPayloadType.Enrolment, + ApiEventPayloadType.Authorization, ApiEventPayloadType.FingerprintCapture, ApiEventPayloadType.OneToOneMatch, + ApiEventPayloadType.OneToManyMatch, ApiEventPayloadType.PersonCreation, ApiEventPayloadType.AlertScreen, + ApiEventPayloadType.GuidSelection, ApiEventPayloadType.ConnectivitySnapshot, ApiEventPayloadType.Refusal, + ApiEventPayloadType.CandidateRead, ApiEventPayloadType.ScannerConnection, ApiEventPayloadType.Vero2InfoSnapshot, + ApiEventPayloadType.ScannerFirmwareUpdate, ApiEventPayloadType.InvalidIntent, ApiEventPayloadType.SuspiciousIntent, + ApiEventPayloadType.IntentParsing, ApiEventPayloadType.CompletionCheck, ApiEventPayloadType.FaceOnboardingComplete, + ApiEventPayloadType.FaceFallbackCapture, ApiEventPayloadType.FaceCapture, ApiEventPayloadType.FaceCaptureConfirmation, + ApiEventPayloadType.FingerprintCaptureBiometrics, ApiEventPayloadType.FaceCaptureBiometrics, + ApiEventPayloadType.EventDownSyncRequest, ApiEventPayloadType.EventUpSyncRequest, ApiEventPayloadType.LicenseCheck, + ApiEventPayloadType.AgeGroupSelection, ApiEventPayloadType.BiometricReferenceCreation, null, -> { // ADD TEST FOR NEW EVENT IN THIS CLASS diff --git a/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt b/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt index 744fbd0305..ddeb31f1c8 100644 --- a/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt +++ b/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt @@ -16,6 +16,7 @@ import com.simprints.infra.events.event.domain.models.AuthenticationEvent.Authen import com.simprints.infra.events.event.domain.models.AuthorizationEvent import com.simprints.infra.events.event.domain.models.AuthorizationEvent.AuthorizationPayload import com.simprints.infra.events.event.domain.models.AuthorizationEvent.AuthorizationPayload.AuthorizationResult.AUTHORIZED +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent import com.simprints.infra.events.event.domain.models.CandidateReadEvent import com.simprints.infra.events.event.domain.models.CandidateReadEvent.CandidateReadPayload.LocalResult.FOUND import com.simprints.infra.events.event.domain.models.CandidateReadEvent.CandidateReadPayload.RemoteResult.NOT_FOUND @@ -453,3 +454,10 @@ fun createAgeGroupSelectionEvent() = AgeGroupSelectionEvent( endedAt = ENDED_AT, subjectAgeGroup = AgeGroupSelectionEvent.AgeGroup(1, 2), ) + +fun createBiometricReferenceCreationEvent() = BiometricReferenceCreationEvent( + startTime = CREATED_AT, + referenceId = GUID1, + modality = BiometricReferenceCreationEvent.BiometricReferenceModality.FACE, + captureIds = listOf(GUID1, GUID2), +) diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/BiometricReferenceCreationEvent.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/BiometricReferenceCreationEvent.kt new file mode 100644 index 0000000000..9e563d2408 --- /dev/null +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/BiometricReferenceCreationEvent.kt @@ -0,0 +1,57 @@ +package com.simprints.infra.events.event.domain.models + +import androidx.annotation.Keep +import com.simprints.core.domain.tokenization.TokenizableString +import com.simprints.core.tools.time.Timestamp +import com.simprints.infra.config.store.models.TokenKeyType +import com.simprints.infra.events.event.domain.models.EventType.BIOMETRIC_REFERENCE_CREATION +import java.util.UUID + +@Keep +data class BiometricReferenceCreationEvent( + override val id: String = UUID.randomUUID().toString(), + override val payload: BiometricReferenceCreationPayload, + override val type: EventType, + override var scopeId: String? = null, + override var projectId: String? = null, +) : Event() { + constructor( + startTime: Timestamp, + referenceId: String, + modality: BiometricReferenceModality, + captureIds: List, + ) : this( + UUID.randomUUID().toString(), + BiometricReferenceCreationPayload( + createdAt = startTime, + eventVersion = EVENT_VERSION, + id = referenceId, + modality = modality, + captureIds = captureIds, + ), + BIOMETRIC_REFERENCE_CREATION, + ) + + override fun getTokenizableFields(): Map = emptyMap() + + override fun setTokenizedFields(map: Map): Event = this + + data class BiometricReferenceCreationPayload( + override val createdAt: Timestamp, + override val eventVersion: Int, + val id: String, + val modality: BiometricReferenceModality, + val captureIds: List, + override val endedAt: Timestamp? = null, + override val type: EventType = BIOMETRIC_REFERENCE_CREATION, + ) : EventPayload() + + enum class BiometricReferenceModality { + FACE, + FINGERPRINT, + } + + companion object { + const val EVENT_VERSION = 1 + } +} diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/Event.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/Event.kt index 9df1bd7a5e..4227b4734f 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/Event.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/Event.kt @@ -9,6 +9,7 @@ import com.simprints.infra.events.event.domain.models.EventType.Companion.AGE_GR import com.simprints.infra.events.event.domain.models.EventType.Companion.ALERT_SCREEN_KEY import com.simprints.infra.events.event.domain.models.EventType.Companion.AUTHENTICATION_KEY import com.simprints.infra.events.event.domain.models.EventType.Companion.AUTHORIZATION_KEY +import com.simprints.infra.events.event.domain.models.EventType.Companion.BIOMETRIC_REFERENCE_CREATION_KEY import com.simprints.infra.events.event.domain.models.EventType.Companion.CALLBACK_CONFIRMATION_KEY import com.simprints.infra.events.event.domain.models.EventType.Companion.CALLBACK_ENROLMENT_KEY import com.simprints.infra.events.event.domain.models.EventType.Companion.CALLBACK_ERROR_KEY @@ -138,6 +139,7 @@ import com.simprints.infra.events.event.domain.models.upsync.EventUpSyncRequestE JsonSubTypes.Type(value = EventUpSyncRequestEvent::class, name = EVENT_UP_SYNC_REQUEST_KEY), JsonSubTypes.Type(value = LicenseCheckEvent::class, name = LICENSE_CHECK_KEY), JsonSubTypes.Type(value = AgeGroupSelectionEvent::class, name = AGE_GROUP_SELECTION_KEY), + JsonSubTypes.Type(value = BiometricReferenceCreationEvent::class, name = BIOMETRIC_REFERENCE_CREATION_KEY), ) abstract class Event { abstract val id: String diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventPayload.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventPayload.kt index 518cbff56a..fb44667f1e 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventPayload.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventPayload.kt @@ -7,6 +7,7 @@ import com.simprints.infra.events.event.domain.models.AgeGroupSelectionEvent.Age import com.simprints.infra.events.event.domain.models.AlertScreenEvent.AlertScreenPayload import com.simprints.infra.events.event.domain.models.AuthenticationEvent.AuthenticationPayload import com.simprints.infra.events.event.domain.models.AuthorizationEvent.AuthorizationPayload +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent.BiometricReferenceCreationPayload import com.simprints.infra.events.event.domain.models.CandidateReadEvent.CandidateReadPayload import com.simprints.infra.events.event.domain.models.CompletionCheckEvent.CompletionCheckPayload import com.simprints.infra.events.event.domain.models.ConnectivitySnapshotEvent.ConnectivitySnapshotPayload @@ -89,6 +90,7 @@ import com.simprints.infra.events.event.domain.models.upsync.EventUpSyncRequestE JsonSubTypes.Type(value = EventUpSyncRequestPayload::class, name = Companion.EVENT_UP_SYNC_REQUEST_KEY), JsonSubTypes.Type(value = LicenseCheckEventPayload::class, name = Companion.LICENSE_CHECK_KEY), JsonSubTypes.Type(value = AgeGroupSelectionPayload::class, name = Companion.AGE_GROUP_SELECTION_KEY), + JsonSubTypes.Type(value = BiometricReferenceCreationPayload::class, name = Companion.BIOMETRIC_REFERENCE_CREATION_KEY), ) abstract class EventPayload { abstract val type: EventType diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventType.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventType.kt index 4031238052..02d9c1f13e 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventType.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventType.kt @@ -133,6 +133,9 @@ enum class EventType { // key added: AGE_GROUP_SELECTION_KEY AGE_GROUP_SELECTION, + + // key added: BIOMETRIC_REFERENCE_CREATION_KEY + BIOMETRIC_REFERENCE_CREATION, ; companion object { @@ -178,5 +181,6 @@ enum class EventType { const val EVENT_UP_SYNC_REQUEST_KEY = "EVENT_UP_SYNC_REQUEST" const val LICENSE_CHECK_KEY = "LICENSE_CHECK" const val AGE_GROUP_SELECTION_KEY = "AGE_GROUP_SELECTION" + const val BIOMETRIC_REFERENCE_CREATION_KEY = "BIOMETRIC_REFERENCE_CREATION" } } diff --git a/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/BiometricReferenceCreationEventTest.kt b/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/BiometricReferenceCreationEventTest.kt new file mode 100644 index 0000000000..cdc82fd457 --- /dev/null +++ b/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/BiometricReferenceCreationEventTest.kt @@ -0,0 +1,34 @@ +package com.simprints.infra.events.event.domain.models + +import com.google.common.truth.Truth.assertThat +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent.Companion.EVENT_VERSION +import com.simprints.infra.events.event.domain.models.EventType.BIOMETRIC_REFERENCE_CREATION +import com.simprints.infra.events.sampledata.SampleDefaults.CREATED_AT +import com.simprints.infra.events.sampledata.SampleDefaults.GUID1 +import com.simprints.infra.events.sampledata.SampleDefaults.GUID2 +import com.simprints.infra.events.sampledata.SampleDefaults.GUID3 +import org.junit.Test + +class BiometricReferenceCreationEventTest { + @Test + fun create_BiometricReferenceCreationEvent() { + val event = BiometricReferenceCreationEvent( + CREATED_AT, + GUID1, + BiometricReferenceCreationEvent.BiometricReferenceModality.FACE, + listOf(GUID2, GUID3), + ) + + assertThat(event.id).isNotNull() + assertThat(event.type).isEqualTo(BIOMETRIC_REFERENCE_CREATION) + with(event.payload) { + assertThat(createdAt).isEqualTo(CREATED_AT) + assertThat(endedAt).isNull() + assertThat(id).isEqualTo(GUID1) + assertThat(modality).isEqualTo(BiometricReferenceCreationEvent.BiometricReferenceModality.FACE) + assertThat(captureIds).containsExactly(GUID2, GUID3) + assertThat(eventVersion).isEqualTo(EVENT_VERSION) + assertThat(type).isEqualTo(BIOMETRIC_REFERENCE_CREATION) + } + } +} diff --git a/infra/events/src/test/resources/all-events/biometric_reference_creation_v1.json b/infra/events/src/test/resources/all-events/biometric_reference_creation_v1.json new file mode 100644 index 0000000000..17f1a8ea9c --- /dev/null +++ b/infra/events/src/test/resources/all-events/biometric_reference_creation_v1.json @@ -0,0 +1,21 @@ +{ + "id": "c96e03c0-f063-4eb0-94ca-833e0a412965", + "type": "BIOMETRIC_REFERENCE_CREATION", + "labels": { + "projectId": "TEST6Oai41ps1pBNrzBL", + "sessionId": "e35c39f9-b81e-48f2-97e7-46ecc8399bb4", + "deviceId": "f2fd8393c0a0be67" + }, + "payload": { + "type": "BIOMETRIC_REFERENCE_CREATION", + "eventVersion": 1, + "createdAt": 9345678901, + "endedAt": 0, + "id": "78d13639-ac13-497f-8d81-4e7b08551c93", + "modality": "FACE", + "captureIds": [ + "4f599e48-bf94-49b9-a752-8992fd77014c", + "bd76b0e6-56d1-4ef2-a119-9a21d90c36dc" + ] + } +} From 267f778e1bdee69c2eb214b5082c4bc58f06266c Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Thu, 6 Feb 2025 11:07:41 +0200 Subject: [PATCH 02/22] MS-871 Generalise reference ID creation to ensure it is consistent regardless of template source --- .../usecases/CreatePersonEventUseCase.kt | 15 ++- .../core/domain/common/ReferenceIds.kt | 38 ++++++++ .../simprints/core/domain/face/FaceSample.kt | 16 ---- .../domain/fingerprint/FingerprintSample.kt | 12 --- .../core/domain/common/ReferenceIdsTest.kt | 93 +++++++++++++++++++ .../core/domain/face/FaceSampleTest.kt | 38 -------- .../fingerprint/FingerprintSampleTest.kt | 46 --------- .../remote/models/face/ApiFaceReference.kt | 5 +- .../fingerprint/ApiFingerprintReference.kt | 5 +- .../subject/EnrolmentRecordCreationEvent.kt | 8 +- 10 files changed, 145 insertions(+), 131 deletions(-) create mode 100644 infra/core/src/main/java/com/simprints/core/domain/common/ReferenceIds.kt create mode 100644 infra/core/src/test/java/com/simprints/core/domain/common/ReferenceIdsTest.kt delete mode 100644 infra/core/src/test/java/com/simprints/core/domain/face/FaceSampleTest.kt delete mode 100644 infra/core/src/test/java/com/simprints/core/domain/fingerprint/FingerprintSampleTest.kt diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCase.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCase.kt index f525fd7e8d..4f11603e3e 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCase.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCase.kt @@ -1,9 +1,7 @@ package com.simprints.feature.orchestrator.usecases -import com.simprints.core.domain.face.FaceSample -import com.simprints.core.domain.face.uniqueId -import com.simprints.core.domain.fingerprint.FingerprintSample -import com.simprints.core.domain.fingerprint.uniqueId +import com.simprints.core.domain.common.faceReferenceId +import com.simprints.core.domain.common.fingerprintReferenceId import com.simprints.core.tools.time.TimeHelper import com.simprints.face.capture.FaceCaptureResult import com.simprints.fingerprint.capture.FingerprintCaptureResult @@ -51,16 +49,15 @@ internal class CreatePersonEventUseCase @Inject constructor( .ifEmpty { null } val fingerprintReferenceId = fingerprintSamplesForPersonCreation .mapNotNull { it.sample } - .map { FingerprintSample(it.fingerIdentifier, it.template, it.templateQualityScore, it.format) } - .uniqueId() + .map { it.templateQualityScore to it.template } + .fingerprintReferenceId() val faceCaptureIds = faceSamplesForPersonCreation .mapNotNull { it.captureEventId } .ifEmpty { null } val faceReferenceId = faceSamplesForPersonCreation - .mapNotNull { it.sample } - .map { FaceSample(it.template, it.format) } - .uniqueId() + .mapNotNull { it.sample?.template } + .faceReferenceId() // If the step results of the current callout do not contain a modality but we have a PersonCreationEvent from the // previous callout (of the same session), we use the modality from the previous callout. This happens when the diff --git a/infra/core/src/main/java/com/simprints/core/domain/common/ReferenceIds.kt b/infra/core/src/main/java/com/simprints/core/domain/common/ReferenceIds.kt new file mode 100644 index 0000000000..27e7b7e716 --- /dev/null +++ b/infra/core/src/main/java/com/simprints/core/domain/common/ReferenceIds.kt @@ -0,0 +1,38 @@ +package com.simprints.core.domain.common + +import com.simprints.core.domain.face.FaceSample +import com.simprints.core.domain.fingerprint.FingerprintSample +import java.util.UUID + +/** + * Generate UUID based on the provided face templates. + */ +fun List.faceReferenceId(): String? = if (isNotEmpty()) { + sortedBy { it.contentHashCode() } + .fold(byteArrayOf()) { acc, template -> acc + template } + .let { UUID.nameUUIDFromBytes(it).toString() } +} else { + null +} + +/** + * Generate UUID based on the provided face samples. + */ +fun List.faceReferenceIdFromSamples(): String? = map { it.template }.faceReferenceId() + +/** + * Generate UUID based on the provided fingerprint templates and template quality scores. + */ +fun List>.fingerprintReferenceId(): String? = if (isNotEmpty()) { + sortedBy { it.first } + .fold(byteArrayOf()) { acc, sample -> acc + sample.second } + .let { UUID.nameUUIDFromBytes(it).toString() } +} else { + null +} + +/** + * Generate UUID based on the provided fingerprint templates. + */ +fun List.fingerprintReferenceIdFromSamples(): String? = + map { it.templateQualityScore to it.template }.fingerprintReferenceId() diff --git a/infra/core/src/main/java/com/simprints/core/domain/face/FaceSample.kt b/infra/core/src/main/java/com/simprints/core/domain/face/FaceSample.kt index a4e931f54a..1ddae8e170 100644 --- a/infra/core/src/main/java/com/simprints/core/domain/face/FaceSample.kt +++ b/infra/core/src/main/java/com/simprints/core/domain/face/FaceSample.kt @@ -25,19 +25,3 @@ data class FaceSample( override fun hashCode(): Int = template.contentHashCode() } - -// Generates a unique id for a list of samples. -// It concats the templates (sorted by quality score) and creates a UUID from that. Or null if there -// are not templates -fun List.uniqueId(): String? = if (this.isNotEmpty()) { - UUID - .nameUUIDFromBytes( - concatTemplates(), - ).toString() -} else { - null -} - -fun List.concatTemplates(): ByteArray = this.sortedBy { it.template.contentHashCode() }.fold(byteArrayOf()) { acc, sample -> - acc + sample.template -} diff --git a/infra/core/src/main/java/com/simprints/core/domain/fingerprint/FingerprintSample.kt b/infra/core/src/main/java/com/simprints/core/domain/fingerprint/FingerprintSample.kt index f76c00913c..ccfdb00a97 100644 --- a/infra/core/src/main/java/com/simprints/core/domain/fingerprint/FingerprintSample.kt +++ b/infra/core/src/main/java/com/simprints/core/domain/fingerprint/FingerprintSample.kt @@ -34,15 +34,3 @@ data class FingerprintSample( return result } } - -// Generates a unique id for a list of samples. -// It concats the templates (sorted by quality score) and creates a UUID from that. -fun List.uniqueId(): String? = if (this.isNotEmpty()) { - UUID.nameUUIDFromBytes(concatTemplates()).toString() -} else { - null -} - -fun List.concatTemplates(): ByteArray = this.sortedBy { it.templateQualityScore }.fold(byteArrayOf()) { acc, sample -> - acc + sample.template -} diff --git a/infra/core/src/test/java/com/simprints/core/domain/common/ReferenceIdsTest.kt b/infra/core/src/test/java/com/simprints/core/domain/common/ReferenceIdsTest.kt new file mode 100644 index 0000000000..a8196fded1 --- /dev/null +++ b/infra/core/src/test/java/com/simprints/core/domain/common/ReferenceIdsTest.kt @@ -0,0 +1,93 @@ +package com.simprints.core.domain.common + +import com.google.common.truth.Truth.assertThat +import com.simprints.core.domain.face.FaceSample +import com.simprints.core.domain.fingerprint.FingerprintSample +import com.simprints.core.domain.fingerprint.IFingerIdentifier +import org.junit.Test +import java.util.UUID +import kotlin.collections.listOf + +class ReferenceIdsTest { + @Test + fun `face reference ID from arrays calculated from correctly`() { + val templates = listOf( + byteArrayOf(2), + byteArrayOf(1), + byteArrayOf(3), + ) + val expected = UUID.nameUUIDFromBytes(byteArrayOf(1, 2, 3)).toString() + + assertThat(templates.faceReferenceId()).isEqualTo(expected) + } + + @Test + fun `face reference ID from samples is calculated correctly`() { + val samples = listOf( + FaceSample( + template = byteArrayOf(2), + format = "", + ), + FaceSample( + template = byteArrayOf(3), + format = "", + ), + FaceSample( + template = byteArrayOf(1), + format = "", + ), + ) + val expected = UUID.nameUUIDFromBytes(byteArrayOf(1, 2, 3)).toString() + + assertThat(samples.faceReferenceIdFromSamples()).isEqualTo(expected) + } + + @Test + fun `face reference ID returns null for empty list`() { + assertThat(listOf().faceReferenceId()).isNull() + } + + @Test + fun `fingerprint reference ID from arrays calculated from correctly`() { + val samples = listOf( + 3 to byteArrayOf(31, 32), + 2 to byteArrayOf(21, 22), + 1 to byteArrayOf(1, 2), + ) + val expected = UUID.nameUUIDFromBytes(byteArrayOf(1, 2, 21, 22, 31, 32)).toString() + + assertThat(samples.fingerprintReferenceId()).isEqualTo(expected) + } + + @Test + fun `fingerprint reference ID from samples calculated from correctly`() { + val samples = listOf( + FingerprintSample( + fingerIdentifier = IFingerIdentifier.LEFT_3RD_FINGER, + template = byteArrayOf(31, 32), + templateQualityScore = 3, + format = "", + ), + FingerprintSample( + fingerIdentifier = IFingerIdentifier.LEFT_3RD_FINGER, + template = byteArrayOf(1, 2), + templateQualityScore = 1, + format = "", + ), + FingerprintSample( + fingerIdentifier = IFingerIdentifier.LEFT_3RD_FINGER, + template = byteArrayOf(21, 22), + templateQualityScore = 2, + format = "", + ), + ) + val expected = UUID.nameUUIDFromBytes(byteArrayOf(1, 2, 21, 22, 31, 32)).toString() + + assertThat(samples.fingerprintReferenceIdFromSamples()).isEqualTo(expected) + } + + @Test + fun `fingerprint reference ID returns null for empty list`() { + assertThat(listOf>().fingerprintReferenceId()).isNull() + } +} diff --git a/infra/core/src/test/java/com/simprints/core/domain/face/FaceSampleTest.kt b/infra/core/src/test/java/com/simprints/core/domain/face/FaceSampleTest.kt deleted file mode 100644 index 44e637efca..0000000000 --- a/infra/core/src/test/java/com/simprints/core/domain/face/FaceSampleTest.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.simprints.core.domain.face - -import com.google.common.truth.Truth.assertThat -import org.junit.Test - -class FaceSampleTest { - @Test - fun testUniqueId() { - assertThat(listOf().uniqueId()).isNull() - assertThat( - listOf( - FaceSample( - template = byteArrayOf(1, 2), - format = "", - ), - ).uniqueId(), - ).isNotNull() - } - - @Test - fun testConcatTemplates() { - val samples = listOf( - FaceSample( - template = byteArrayOf(2), - format = "", - ), - FaceSample( - template = byteArrayOf(1), - format = "", - ), - FaceSample( - template = byteArrayOf(3), - format = "", - ), - ) - assertThat(samples.concatTemplates()).isEqualTo(byteArrayOf(1, 2, 3)) - } -} diff --git a/infra/core/src/test/java/com/simprints/core/domain/fingerprint/FingerprintSampleTest.kt b/infra/core/src/test/java/com/simprints/core/domain/fingerprint/FingerprintSampleTest.kt deleted file mode 100644 index 41a8e0b227..0000000000 --- a/infra/core/src/test/java/com/simprints/core/domain/fingerprint/FingerprintSampleTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.simprints.core.domain.fingerprint - -import com.google.common.truth.Truth.assertThat -import org.junit.Test - -class FingerprintSampleTest { - @Test - fun testUniqueId() { - assertThat(listOf().uniqueId()).isNull() - assertThat( - listOf( - FingerprintSample( - fingerIdentifier = IFingerIdentifier.LEFT_3RD_FINGER, - template = byteArrayOf(1, 2), - templateQualityScore = 1, - format = "", - ), - ).uniqueId(), - ).isNotNull() - } - - @Test - fun testConcatTemplates() { - val fingerprintSample = listOf( - FingerprintSample( - fingerIdentifier = IFingerIdentifier.LEFT_3RD_FINGER, - template = byteArrayOf(31, 32), - templateQualityScore = 3, - format = "", - ), - FingerprintSample( - fingerIdentifier = IFingerIdentifier.LEFT_3RD_FINGER, - template = byteArrayOf(1, 2), - templateQualityScore = 1, - format = "", - ), - FingerprintSample( - fingerIdentifier = IFingerIdentifier.LEFT_3RD_FINGER, - template = byteArrayOf(21, 22), - templateQualityScore = 2, - format = "", - ), - ) - assertThat(fingerprintSample.concatTemplates()).isEqualTo(byteArrayOf(1, 2, 21, 22, 31, 32)) - } -} diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/face/ApiFaceReference.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/face/ApiFaceReference.kt index d15cb9b5cd..173b06d6db 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/face/ApiFaceReference.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/face/ApiFaceReference.kt @@ -1,11 +1,10 @@ package com.simprints.infra.enrolment.records.repository.remote.models.face import androidx.annotation.Keep +import com.simprints.core.domain.common.faceReferenceIdFromSamples import com.simprints.core.domain.face.FaceSample -import com.simprints.core.domain.face.concatTemplates import com.simprints.core.tools.utils.EncodingUtils import com.simprints.infra.enrolment.records.repository.remote.models.ApiBiometricReference -import java.util.UUID @Keep internal data class ApiFaceReference( @@ -17,7 +16,7 @@ internal data class ApiFaceReference( internal fun List.toApi(encoder: EncodingUtils): ApiFaceReference? = if (isNotEmpty()) { ApiFaceReference( - UUID.nameUUIDFromBytes(concatTemplates()).toString(), + faceReferenceIdFromSamples().orEmpty(), map { ApiFaceTemplate(encoder.byteArrayToBase64(it.template)) }, diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/fingerprint/ApiFingerprintReference.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/fingerprint/ApiFingerprintReference.kt index 27d5dbcc5f..52151bff72 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/fingerprint/ApiFingerprintReference.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/fingerprint/ApiFingerprintReference.kt @@ -1,11 +1,10 @@ package com.simprints.infra.enrolment.records.repository.remote.models.fingerprint import androidx.annotation.Keep +import com.simprints.core.domain.common.fingerprintReferenceIdFromSamples import com.simprints.core.domain.fingerprint.FingerprintSample -import com.simprints.core.domain.fingerprint.concatTemplates import com.simprints.core.tools.utils.EncodingUtils import com.simprints.infra.enrolment.records.repository.remote.models.ApiBiometricReference -import java.util.UUID @Keep internal data class ApiFingerprintReference( @@ -17,7 +16,7 @@ internal data class ApiFingerprintReference( internal fun List.toApi(encoder: EncodingUtils): ApiFingerprintReference? = if (isNotEmpty()) { ApiFingerprintReference( - UUID.nameUUIDFromBytes(concatTemplates()).toString(), + fingerprintReferenceIdFromSamples() ?: "", map { ApiFingerprintTemplate( it.templateQualityScore, diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordCreationEvent.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordCreationEvent.kt index 914dce84f2..4964be851c 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordCreationEvent.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordCreationEvent.kt @@ -1,10 +1,10 @@ package com.simprints.infra.events.event.domain.models.subject import androidx.annotation.Keep +import com.simprints.core.domain.common.faceReferenceIdFromSamples +import com.simprints.core.domain.common.fingerprintReferenceIdFromSamples import com.simprints.core.domain.face.FaceSample -import com.simprints.core.domain.face.uniqueId import com.simprints.core.domain.fingerprint.FingerprintSample -import com.simprints.core.domain.fingerprint.uniqueId import com.simprints.core.domain.tokenization.TokenizableString import com.simprints.core.tools.utils.EncodingUtils import java.util.UUID @@ -64,7 +64,7 @@ data class EnrolmentRecordCreationEvent( encoder: EncodingUtils, ) = if (fingerprintSamples.isNotEmpty()) { FingerprintReference( - fingerprintSamples.uniqueId() ?: "", + fingerprintSamples.fingerprintReferenceIdFromSamples() ?: "", fingerprintSamples.map { FingerprintTemplate( it.templateQualityScore, @@ -83,7 +83,7 @@ data class EnrolmentRecordCreationEvent( encoder: EncodingUtils, ) = if (faceSamples.isNotEmpty()) { FaceReference( - faceSamples.uniqueId() ?: "", + faceSamples.faceReferenceIdFromSamples() ?: "", faceSamples.map { FaceTemplate( encoder.byteArrayToBase64(it.template), From 1d5177128950d5be310492da19f6a95d9e3afddf Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Thu, 6 Feb 2025 15:49:25 +0200 Subject: [PATCH 03/22] MS-871 Remove ENROLMENT_V1 event definition, it has been long enough --- .../remote/models/ApiEnrolmentPayloadV1.kt | 18 -------- .../event/remote/models/ApiEventPayload.kt | 3 -- .../remote/models/ApiEventPayloadType.kt | 3 +- .../eventsync/event/EventValidationUtils.kt | 10 ---- .../models/ApiEnrolmentPayloadV1Test.kt | 16 ------- .../MapDomainEventToApiUseCaseTest.kt | 13 +----- .../events/sampledata/EventFactoryUtils.kt | 6 --- .../event/domain/models/EnrolmentEventV1.kt | 46 ------------------- .../infra/events/event/domain/models/Event.kt | 2 - .../event/domain/models/EventPayload.kt | 1 - .../events/event/domain/models/EventType.kt | 4 -- .../local/migrations/EventMigration1to2.kt | 7 ++- .../domain/models/EnrolmentEventV1Test.kt | 23 ---------- .../event/domain/models/EventPayloadTest.kt | 1 - .../migrations/EventMigration1to2Test.kt | 3 +- .../resources/all-events/enrolment_v1.json | 16 ------- 16 files changed, 6 insertions(+), 166 deletions(-) delete mode 100644 infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV1.kt delete mode 100644 infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV1Test.kt delete mode 100644 infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV1Test.kt delete mode 100644 infra/events/src/test/resources/all-events/enrolment_v1.json diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV1.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV1.kt deleted file mode 100644 index 12e670fdd3..0000000000 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV1.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.simprints.infra.eventsync.event.remote.models - -import androidx.annotation.Keep -import com.simprints.infra.config.store.models.TokenKeyType -import com.simprints.infra.events.event.domain.models.EnrolmentEventV1 - -@Keep -internal data class ApiEnrolmentPayloadV1( - override val startTime: ApiTimestamp, - val personId: String, -) : ApiEventPayload(startTime) { - constructor(domainPayload: EnrolmentEventV1.EnrolmentPayload) : this( - domainPayload.createdAt.fromDomainToApi(), - domainPayload.personId, - ) - - override fun getTokenizedFieldJsonPath(tokenKeyType: TokenKeyType): String? = null // this payload doesn't have tokenizable fields -} diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt index 3ef4f323f7..e2345c2c81 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt @@ -13,7 +13,6 @@ import com.simprints.infra.events.event.domain.models.CandidateReadEvent.Candida import com.simprints.infra.events.event.domain.models.CompletionCheckEvent.CompletionCheckPayload import com.simprints.infra.events.event.domain.models.ConnectivitySnapshotEvent.ConnectivitySnapshotPayload import com.simprints.infra.events.event.domain.models.ConsentEvent.ConsentPayload -import com.simprints.infra.events.event.domain.models.EnrolmentEventV1 import com.simprints.infra.events.event.domain.models.EnrolmentEventV2 import com.simprints.infra.events.event.domain.models.EventPayload import com.simprints.infra.events.event.domain.models.EventType.AGE_GROUP_SELECTION @@ -36,7 +35,6 @@ import com.simprints.infra.events.event.domain.models.EventType.CANDIDATE_READ import com.simprints.infra.events.event.domain.models.EventType.COMPLETION_CHECK import com.simprints.infra.events.event.domain.models.EventType.CONNECTIVITY_SNAPSHOT import com.simprints.infra.events.event.domain.models.EventType.CONSENT -import com.simprints.infra.events.event.domain.models.EventType.ENROLMENT_V1 import com.simprints.infra.events.event.domain.models.EventType.ENROLMENT_V2 import com.simprints.infra.events.event.domain.models.EventType.EVENT_DOWN_SYNC_REQUEST import com.simprints.infra.events.event.domain.models.EventType.EVENT_UP_SYNC_REQUEST @@ -146,7 +144,6 @@ internal abstract class ApiEventPayload( internal fun EventPayload.fromDomainToApi(): ApiEventPayload = when (this.type) { AUTHENTICATION -> ApiAuthenticationPayload(this as AuthenticationPayload) CONSENT -> ApiConsentPayload(this as ConsentPayload) - ENROLMENT_V1 -> ApiEnrolmentPayloadV1(this as EnrolmentEventV1.EnrolmentPayload) ENROLMENT_V2 -> ApiEnrolmentPayloadV2(this as EnrolmentEventV2.EnrolmentPayload) AUTHORIZATION -> ApiAuthorizationPayload(this as AuthorizationPayload) FINGERPRINT_CAPTURE -> ApiFingerprintCapturePayload(this as FingerprintCaptureEvent.FingerprintCapturePayload) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayloadType.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayloadType.kt index 72ba14267c..fa795d08c9 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayloadType.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayloadType.kt @@ -22,7 +22,6 @@ import com.simprints.infra.events.event.domain.models.EventType.CANDIDATE_READ import com.simprints.infra.events.event.domain.models.EventType.COMPLETION_CHECK import com.simprints.infra.events.event.domain.models.EventType.CONNECTIVITY_SNAPSHOT import com.simprints.infra.events.event.domain.models.EventType.CONSENT -import com.simprints.infra.events.event.domain.models.EventType.ENROLMENT_V1 import com.simprints.infra.events.event.domain.models.EventType.ENROLMENT_V2 import com.simprints.infra.events.event.domain.models.EventType.EVENT_DOWN_SYNC_REQUEST import com.simprints.infra.events.event.domain.models.EventType.EVENT_UP_SYNC_REQUEST @@ -191,7 +190,7 @@ internal enum class ApiEventPayloadType { internal fun EventType.fromDomainToApi(): ApiEventPayloadType = when (this) { AUTHENTICATION -> ApiEventPayloadType.Authentication CONSENT -> ApiEventPayloadType.Consent - ENROLMENT_V1, ENROLMENT_V2 -> ApiEventPayloadType.Enrolment + ENROLMENT_V2 -> ApiEventPayloadType.Enrolment AUTHORIZATION -> ApiEventPayloadType.Authorization FINGERPRINT_CAPTURE -> ApiEventPayloadType.FingerprintCapture ONE_TO_ONE_MATCH -> ApiEventPayloadType.OneToOneMatch diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt index a7256f943e..362e81fc85 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt @@ -303,16 +303,6 @@ fun validateConsentEventApiModel(json: JSONObject) { } } -fun validateEnrolmentEventV1ApiModel(json: JSONObject) { - validateCommonParams(json, "Enrolment", 2) - - with(json.getJSONObject("payload")) { - validateTimestamp(getJSONObject("startTime")) - assertThat(getString("personId").isValidGuid()).isTrue() - assertThat(length()).isEqualTo(2) - } -} - fun validateEnrolmentEventV2ApiModel(json: JSONObject) { validateCommonParams(json, "Enrolment", 3) diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV1Test.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV1Test.kt deleted file mode 100644 index a0f3b523d4..0000000000 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV1Test.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.simprints.infra.eventsync.event.remote.models - -import com.google.common.truth.Truth.assertThat -import com.simprints.infra.config.store.models.TokenKeyType -import io.mockk.mockk -import org.junit.Test - -class ApiEnrolmentPayloadV1Test { - @Test - fun `when getTokenizedFieldJsonPath is invoked, null is returned`() { - val payload = ApiEnrolmentPayloadV1(domainPayload = mockk(relaxed = true)) - TokenKeyType.values().forEach { - assertThat(payload.getTokenizedFieldJsonPath(it)).isNull() - } - } -} diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventToApiUseCaseTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventToApiUseCaseTest.kt index d8da5c05ab..2b7caddd6b 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventToApiUseCaseTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventToApiUseCaseTest.kt @@ -24,7 +24,6 @@ import com.simprints.infra.events.sampledata.createConnectivitySnapshotEvent import com.simprints.infra.events.sampledata.createConsentEvent import com.simprints.infra.events.sampledata.createEnrolmentCallbackEvent import com.simprints.infra.events.sampledata.createEnrolmentCalloutEvent -import com.simprints.infra.events.sampledata.createEnrolmentEventV1 import com.simprints.infra.events.sampledata.createEnrolmentEventV2 import com.simprints.infra.events.sampledata.createEventDownSyncRequestEvent import com.simprints.infra.events.sampledata.createEventUpSyncRequestEvent @@ -102,7 +101,6 @@ import com.simprints.infra.eventsync.event.validateCompletionCheckEventApiModel import com.simprints.infra.eventsync.event.validateConnectivitySnapshotEventApiModel import com.simprints.infra.eventsync.event.validateConsentEventApiModel import com.simprints.infra.eventsync.event.validateDownSyncRequestEventApiModel -import com.simprints.infra.eventsync.event.validateEnrolmentEventV1ApiModel import com.simprints.infra.eventsync.event.validateEnrolmentEventV2ApiModel import com.simprints.infra.eventsync.event.validateFaceCaptureBiometricsEventApiModel import com.simprints.infra.eventsync.event.validateFaceCaptureConfirmationEventApiModel @@ -317,15 +315,6 @@ internal class MapDomainEventToApiUseCaseTest { validateConsentEventApiModel(json) } - @Test - fun validateEnrolmentV1_enrolmentEventApiModel() { - val event = createEnrolmentEventV1() - val apiEvent = useCase(event, project) - val json = JSONObject(jackson.writeValueAsString(apiEvent)) - - validateEnrolmentEventV1ApiModel(json) - } - @Test fun validateEnrolmentV2_enrolmentEventApiModel() { val event = createEnrolmentEventV2() @@ -527,7 +516,7 @@ internal class MapDomainEventToApiUseCaseTest { @Test fun validate_biometricReferenceCreationEventApiModel() { val event = createBiometricReferenceCreationEvent() - val apiEvent = event.fromDomainToApi() + val apiEvent = useCase(event, project) val json = JSONObject(jackson.writeValueAsString(apiEvent)) validateBiometricReferenceCreationEventApiModel(json) diff --git a/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt b/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt index ddeb31f1c8..50fcb1485d 100644 --- a/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt +++ b/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt @@ -25,7 +25,6 @@ import com.simprints.infra.events.event.domain.models.ConnectivitySnapshotEvent import com.simprints.infra.events.event.domain.models.ConsentEvent import com.simprints.infra.events.event.domain.models.ConsentEvent.ConsentPayload.Result.ACCEPTED import com.simprints.infra.events.event.domain.models.ConsentEvent.ConsentPayload.Type.INDIVIDUAL -import com.simprints.infra.events.event.domain.models.EnrolmentEventV1 import com.simprints.infra.events.event.domain.models.EnrolmentEventV2 import com.simprints.infra.events.event.domain.models.Event import com.simprints.infra.events.event.domain.models.EventType @@ -317,11 +316,6 @@ fun createEnrolmentEventV2() = EnrolmentEventV2( GUID2, ) -fun createEnrolmentEventV1() = EnrolmentEventV1( - CREATED_AT, - GUID1, -) - fun createFingerprintCaptureEvent() = FingerprintCaptureEvent( createdAt = CREATED_AT, endTime = ENDED_AT, diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV1.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV1.kt index f5599fbb85..e69de29bb2 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV1.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV1.kt @@ -1,46 +0,0 @@ -package com.simprints.infra.events.event.domain.models - -import androidx.annotation.Keep -import com.simprints.core.domain.tokenization.TokenizableString -import com.simprints.core.tools.time.Timestamp -import com.simprints.infra.config.store.models.TokenKeyType -import com.simprints.infra.events.event.domain.models.EventType.ENROLMENT_V1 -import java.util.UUID - -@Keep -@Deprecated("Used only for the migration before 2021.1.0") -data class EnrolmentEventV1( - override val id: String = UUID.randomUUID().toString(), - override val payload: EnrolmentPayload, - override val type: EventType, - override var scopeId: String? = null, - override var projectId: String? = null, -) : Event() { - constructor( - createdAt: Timestamp, - personId: String, - ) : this( - UUID.randomUUID().toString(), - EnrolmentPayload(createdAt, EVENT_VERSION, personId), - ENROLMENT_V1, - ) - - override fun getTokenizableFields(): Map = emptyMap() - - override fun setTokenizedFields(map: Map) = this // No tokenized fields - - @Keep - data class EnrolmentPayload( - override val createdAt: Timestamp, - override val eventVersion: Int, - val personId: String, - override val endedAt: Timestamp? = null, - override val type: EventType = ENROLMENT_V1, - ) : EventPayload() { - override fun toSafeString(): String = "person ID: $personId" - } - - companion object { - const val EVENT_VERSION = 2 - } -} diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/Event.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/Event.kt index 4227b4734f..37799055c7 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/Event.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/Event.kt @@ -25,7 +25,6 @@ import com.simprints.infra.events.event.domain.models.EventType.Companion.CANDID import com.simprints.infra.events.event.domain.models.EventType.Companion.COMPLETION_CHECK_KEY import com.simprints.infra.events.event.domain.models.EventType.Companion.CONNECTIVITY_SNAPSHOT_KEY import com.simprints.infra.events.event.domain.models.EventType.Companion.CONSENT_KEY -import com.simprints.infra.events.event.domain.models.EventType.Companion.ENROLMENT_V1_KEY import com.simprints.infra.events.event.domain.models.EventType.Companion.ENROLMENT_V2_KEY import com.simprints.infra.events.event.domain.models.EventType.Companion.EVENT_DOWN_SYNC_REQUEST_KEY import com.simprints.infra.events.event.domain.models.EventType.Companion.EVENT_UP_SYNC_REQUEST_KEY @@ -114,7 +113,6 @@ import com.simprints.infra.events.event.domain.models.upsync.EventUpSyncRequestE JsonSubTypes.Type(value = CompletionCheckEvent::class, name = COMPLETION_CHECK_KEY), JsonSubTypes.Type(value = ConnectivitySnapshotEvent::class, name = CONNECTIVITY_SNAPSHOT_KEY), JsonSubTypes.Type(value = ConsentEvent::class, name = CONSENT_KEY), - JsonSubTypes.Type(value = EnrolmentEventV1::class, name = ENROLMENT_V1_KEY), JsonSubTypes.Type(value = EnrolmentEventV2::class, name = ENROLMENT_V2_KEY), JsonSubTypes.Type(value = FingerprintCaptureEvent::class, name = FINGERPRINT_CAPTURE_KEY), JsonSubTypes.Type( diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventPayload.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventPayload.kt index fb44667f1e..2c38afcda4 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventPayload.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventPayload.kt @@ -71,7 +71,6 @@ import com.simprints.infra.events.event.domain.models.upsync.EventUpSyncRequestE JsonSubTypes.Type(value = CompletionCheckPayload::class, name = EventType.COMPLETION_CHECK_KEY), JsonSubTypes.Type(value = ConnectivitySnapshotPayload::class, name = EventType.CONNECTIVITY_SNAPSHOT_KEY), JsonSubTypes.Type(value = ConsentPayload::class, name = EventType.CONSENT_KEY), - JsonSubTypes.Type(value = EnrolmentEventV1.EnrolmentPayload::class, name = EventType.ENROLMENT_V1_KEY), JsonSubTypes.Type(value = EnrolmentEventV2.EnrolmentPayload::class, name = EventType.ENROLMENT_V2_KEY), JsonSubTypes.Type(value = FingerprintCapturePayload::class, name = EventType.FINGERPRINT_CAPTURE_KEY), JsonSubTypes.Type(value = FingerprintCaptureBiometricsPayload::class, name = EventType.FINGERPRINT_CAPTURE_BIOMETRICS_KEY), diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventType.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventType.kt index 02d9c1f13e..fad6d3113b 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventType.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventType.kt @@ -14,9 +14,6 @@ enum class EventType { // key added: CONSENT_KEY CONSENT, - // key added: ENROLMENT_V1_KEY - ENROLMENT_V1, - // key added: ENROLMENT_V1_KE2 ENROLMENT_V2, @@ -157,7 +154,6 @@ enum class EventType { const val FACE_CAPTURE_CONFIRMATION_KEY = "FACE_CAPTURE_CONFIRMATION" const val AUTHENTICATION_KEY = "AUTHENTICATION" const val CONSENT_KEY = "CONSENT" - const val ENROLMENT_V1_KEY = "ENROLMENT_V1" const val ENROLMENT_V2_KEY = "ENROLMENT_V2" const val AUTHORIZATION_KEY = "AUTHORIZATION" const val FINGERPRINT_CAPTURE_KEY = "FINGERPRINT_CAPTURE" diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/local/migrations/EventMigration1to2.kt b/infra/events/src/main/java/com/simprints/infra/events/event/local/migrations/EventMigration1to2.kt index 32be86a3ac..62c96b9bf6 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/local/migrations/EventMigration1to2.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/local/migrations/EventMigration1to2.kt @@ -4,7 +4,6 @@ import android.database.Cursor import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import com.simprints.core.tools.extentions.getStringWithColumnName -import com.simprints.infra.events.event.domain.models.EventType.ENROLMENT_V1 import com.simprints.infra.logging.LoggingConstants.CrashReportTag.MIGRATION import com.simprints.infra.logging.Simber import org.json.JSONObject @@ -59,10 +58,10 @@ internal class EventMigration1to2 : Migration(1, 2) { ) { val jsonData = it.getStringWithColumnName(DB_EVENT_JSON_FIELD) jsonData?.let { - val originalJson = JSONObject(jsonData).put(DB_EVENT_JSON_EVENT_TYPE, ENROLMENT_V1) + val originalJson = JSONObject(jsonData).put(DB_EVENT_JSON_EVENT_TYPE, "ENROLMENT_V1") val newPayload = originalJson.getJSONObject(DB_EVENT_JSON_EVENT_PAYLOAD).put( DB_EVENT_JSON_EVENT_TYPE, - ENROLMENT_V1, + "ENROLMENT_V1", ) val newJson = originalJson.put(DB_EVENT_JSON_EVENT_PAYLOAD, newPayload) database.execSQL("UPDATE DbEvent SET eventJson = ? WHERE id = ?", arrayOf(newJson, id)) @@ -74,7 +73,7 @@ internal class EventMigration1to2 : Migration(1, 2) { database: SupportSQLiteDatabase, ) { id?.let { - database.execSQL("UPDATE DbEvent SET type = ? WHERE id = ?", arrayOf(ENROLMENT_V1, it)) + database.execSQL("UPDATE DbEvent SET type = ? WHERE id = ?", arrayOf("ENROLMENT_V1", it)) } } diff --git a/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV1Test.kt b/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV1Test.kt deleted file mode 100644 index dbfd90008a..0000000000 --- a/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV1Test.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.simprints.infra.events.event.domain.models - -import com.google.common.truth.Truth.assertThat -import com.simprints.infra.events.event.domain.models.EventType.ENROLMENT_V1 -import com.simprints.infra.events.sampledata.SampleDefaults.CREATED_AT -import com.simprints.infra.events.sampledata.SampleDefaults.GUID2 -import org.junit.Test - -class EnrolmentEventV1Test { - @Test - fun create_EnrolmentEvent() { - val event = EnrolmentEventV1(CREATED_AT, GUID2) - - assertThat(event.id).isNotNull() - assertThat(event.type).isEqualTo(ENROLMENT_V1) - with(event.payload) { - assertThat(createdAt).isEqualTo(CREATED_AT) - assertThat(eventVersion).isEqualTo(EnrolmentEventV1.EVENT_VERSION) - assertThat(type).isEqualTo(ENROLMENT_V1) - assertThat(personId).isEqualTo(GUID2) - } - } -} diff --git a/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EventPayloadTest.kt b/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EventPayloadTest.kt index 7cf9386f9c..e9df4113cd 100644 --- a/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EventPayloadTest.kt +++ b/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EventPayloadTest.kt @@ -192,7 +192,6 @@ class EventPayloadTest { ), ), ConsentEvent(CREATED_AT, ENDED_AT, INDIVIDUAL, ACCEPTED), - EnrolmentEventV1(CREATED_AT, GUID2), EnrolmentEventV2( createdAt = CREATED_AT, subjectId = GUID1, diff --git a/infra/events/src/test/java/com/simprints/infra/events/event/local/migrations/EventMigration1to2Test.kt b/infra/events/src/test/java/com/simprints/infra/events/event/local/migrations/EventMigration1to2Test.kt index 1b1769ae49..e2e6297015 100644 --- a/infra/events/src/test/java/com/simprints/infra/events/event/local/migrations/EventMigration1to2Test.kt +++ b/infra/events/src/test/java/com/simprints/infra/events/event/local/migrations/EventMigration1to2Test.kt @@ -9,7 +9,6 @@ import androidx.test.platform.app.InstrumentationRegistry import com.google.common.truth.Truth.assertThat import com.simprints.core.tools.extentions.getStringWithColumnName import com.simprints.core.tools.utils.randomUUID -import com.simprints.infra.events.event.domain.models.EventType.ENROLMENT_V1 import com.simprints.infra.events.event.local.EventRoomDatabase import com.simprints.infra.events.event.local.migrations.MigrationTestingTools.retrieveCursorWithEventById import com.simprints.testtools.unit.robolectric.ShadowAndroidXMultiDex @@ -110,7 +109,7 @@ class EventMigration1to2Test { id: String, ) { val cursor = retrieveCursorWithEventById(db, id) - assertThat(cursor.getStringWithColumnName("type")).isEqualTo(ENROLMENT_V1.toString()) + assertThat(cursor.getStringWithColumnName("type")).isEqualTo("ENROLMENT_V1") val eventJson = JSONObject(cursor.getStringWithColumnName("eventJson")!!) assertThat(eventJson.getString("type")).isEqualTo("ENROLMENT_V1") diff --git a/infra/events/src/test/resources/all-events/enrolment_v1.json b/infra/events/src/test/resources/all-events/enrolment_v1.json deleted file mode 100644 index fe2f523662..0000000000 --- a/infra/events/src/test/resources/all-events/enrolment_v1.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "4d5ec0e9-8aba-46c6-bc7c-c84258066fd8", - "type": "ENROLMENT_V1", - "labels": { - "projectId": "TEST6Oai41ps1pBNrzBL", - "sessionId": "e35c39f9-b81e-48f2-97e7-46ecc8399bb4", - "deviceId": "f2fd8393c0a0be67" - }, - "payload": { - "type": "ENROLMENT_V1", - "eventVersion": 1, - "createdAt": 178967890, - "endedAt": 0, - "personId": "ef2d93dc-5afd-453f-9ea4-add4286530f6" - } -} From 3f4ab3f405af05e1e5d9430c5c586f7b968b11ab Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Thu, 6 Feb 2025 15:51:33 +0200 Subject: [PATCH 04/22] MS-871 Add ENROLMENT_V4 event implementation --- .../remote/models/ApiEnrolmentPayloadV4.kt | 30 ++++++++ .../event/remote/models/ApiEventPayload.kt | 5 +- .../remote/models/ApiEventPayloadType.kt | 5 +- .../eventsync/event/EventValidationUtils.kt | 14 ++++ .../MapDomainEventToApiUseCaseTest.kt | 11 +++ .../events/sampledata/EventFactoryUtils.kt | 10 +++ .../event/domain/models/EnrolmentEventV2.kt | 1 + .../event/domain/models/EnrolmentEventV4.kt | 69 +++++++++++++++++++ .../infra/events/event/domain/models/Event.kt | 2 + .../event/domain/models/EventPayload.kt | 1 + .../events/event/domain/models/EventType.kt | 6 +- .../domain/models/EnrolmentEventV4Test.kt | 37 ++++++++++ .../event/domain/models/EventPayloadTest.kt | 8 +++ .../resources/all-events/enrolment_v4.json | 28 ++++++++ 14 files changed, 223 insertions(+), 4 deletions(-) create mode 100644 infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV4.kt create mode 100644 infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV4.kt create mode 100644 infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV4Test.kt create mode 100644 infra/events/src/test/resources/all-events/enrolment_v4.json diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV4.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV4.kt new file mode 100644 index 0000000000..519e6ed6df --- /dev/null +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV4.kt @@ -0,0 +1,30 @@ +package com.simprints.infra.eventsync.event.remote.models + +import androidx.annotation.Keep +import com.simprints.infra.config.store.models.TokenKeyType +import com.simprints.infra.events.event.domain.models.EnrolmentEventV4 + +@Keep +internal data class ApiEnrolmentPayloadV4( + override val startTime: ApiTimestamp, + val subjectId: String, + val projectId: String, + val moduleId: String, + val attendantId: String, + val biometricReferenceIds: List, +) : ApiEventPayload(startTime) { + constructor(domainPayload: EnrolmentEventV4.EnrolmentPayload) : this( + domainPayload.createdAt.fromDomainToApi(), + domainPayload.subjectId, + domainPayload.projectId, + domainPayload.moduleId.value, + domainPayload.attendantId.value, + domainPayload.biometricReferenceIds, + ) + + override fun getTokenizedFieldJsonPath(tokenKeyType: TokenKeyType): String? = when (tokenKeyType) { + TokenKeyType.AttendantId -> "attendantId" + TokenKeyType.ModuleId -> "moduleId" + TokenKeyType.Unknown -> null + } +} diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt index e2345c2c81..81776dbf76 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayload.kt @@ -14,6 +14,7 @@ import com.simprints.infra.events.event.domain.models.CompletionCheckEvent.Compl import com.simprints.infra.events.event.domain.models.ConnectivitySnapshotEvent.ConnectivitySnapshotPayload import com.simprints.infra.events.event.domain.models.ConsentEvent.ConsentPayload import com.simprints.infra.events.event.domain.models.EnrolmentEventV2 +import com.simprints.infra.events.event.domain.models.EnrolmentEventV4 import com.simprints.infra.events.event.domain.models.EventPayload import com.simprints.infra.events.event.domain.models.EventType.AGE_GROUP_SELECTION import com.simprints.infra.events.event.domain.models.EventType.ALERT_SCREEN @@ -36,6 +37,7 @@ import com.simprints.infra.events.event.domain.models.EventType.COMPLETION_CHECK import com.simprints.infra.events.event.domain.models.EventType.CONNECTIVITY_SNAPSHOT import com.simprints.infra.events.event.domain.models.EventType.CONSENT import com.simprints.infra.events.event.domain.models.EventType.ENROLMENT_V2 +import com.simprints.infra.events.event.domain.models.EventType.ENROLMENT_V4 import com.simprints.infra.events.event.domain.models.EventType.EVENT_DOWN_SYNC_REQUEST import com.simprints.infra.events.event.domain.models.EventType.EVENT_UP_SYNC_REQUEST import com.simprints.infra.events.event.domain.models.EventType.FACE_CAPTURE @@ -114,7 +116,7 @@ import com.simprints.infra.eventsync.event.remote.models.upsync.ApiEventUpSyncRe JsonSubTypes.Type(value = ApiCompletionCheckPayload::class, name = Companion.COMPLETION_CHECK_KEY), JsonSubTypes.Type(value = ApiConnectivitySnapshotPayload::class, name = Companion.CONNECTIVITY_SNAPSHOT_KEY), JsonSubTypes.Type(value = ApiConsentPayload::class, name = Companion.CONSENT_KEY), - JsonSubTypes.Type(value = ApiEnrolmentPayloadV2::class, name = Companion.ENROLMENT_KEY), + JsonSubTypes.Type(value = ApiEnrolmentPayloadV4::class, name = Companion.ENROLMENT_KEY), JsonSubTypes.Type(value = ApiFingerprintCapturePayload::class, name = Companion.FINGERPRINT_CAPTURE_KEY), JsonSubTypes.Type(value = ApiFingerprintCaptureBiometricsPayload::class, name = Companion.FINGERPRINT_CAPTURE_BIOMETRICS_KEY), JsonSubTypes.Type(value = ApiGuidSelectionPayload::class, name = Companion.GUID_SELECTION_KEY), @@ -145,6 +147,7 @@ internal fun EventPayload.fromDomainToApi(): ApiEventPayload = when (this.type) AUTHENTICATION -> ApiAuthenticationPayload(this as AuthenticationPayload) CONSENT -> ApiConsentPayload(this as ConsentPayload) ENROLMENT_V2 -> ApiEnrolmentPayloadV2(this as EnrolmentEventV2.EnrolmentPayload) + ENROLMENT_V4 -> ApiEnrolmentPayloadV4(this as EnrolmentEventV4.EnrolmentPayload) AUTHORIZATION -> ApiAuthorizationPayload(this as AuthorizationPayload) FINGERPRINT_CAPTURE -> ApiFingerprintCapturePayload(this as FingerprintCaptureEvent.FingerprintCapturePayload) ONE_TO_ONE_MATCH -> ApiOneToOneMatchPayload(this as OneToOneMatchPayload) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayloadType.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayloadType.kt index fa795d08c9..9f0dc95bdb 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayloadType.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEventPayloadType.kt @@ -23,6 +23,7 @@ import com.simprints.infra.events.event.domain.models.EventType.COMPLETION_CHECK import com.simprints.infra.events.event.domain.models.EventType.CONNECTIVITY_SNAPSHOT import com.simprints.infra.events.event.domain.models.EventType.CONSENT import com.simprints.infra.events.event.domain.models.EventType.ENROLMENT_V2 +import com.simprints.infra.events.event.domain.models.EventType.ENROLMENT_V4 import com.simprints.infra.events.event.domain.models.EventType.EVENT_DOWN_SYNC_REQUEST import com.simprints.infra.events.event.domain.models.EventType.EVENT_UP_SYNC_REQUEST import com.simprints.infra.events.event.domain.models.EventType.FACE_CAPTURE @@ -190,7 +191,7 @@ internal enum class ApiEventPayloadType { internal fun EventType.fromDomainToApi(): ApiEventPayloadType = when (this) { AUTHENTICATION -> ApiEventPayloadType.Authentication CONSENT -> ApiEventPayloadType.Consent - ENROLMENT_V2 -> ApiEventPayloadType.Enrolment + ENROLMENT_V2, ENROLMENT_V4 -> ApiEventPayloadType.Enrolment AUTHORIZATION -> ApiEventPayloadType.Authorization FINGERPRINT_CAPTURE -> ApiEventPayloadType.FingerprintCapture ONE_TO_ONE_MATCH -> ApiEventPayloadType.OneToOneMatch @@ -239,7 +240,7 @@ internal fun EventType.fromDomainToApi(): ApiEventPayloadType = when (this) { internal fun ApiEventPayloadType.fromApiToDomain(): EventType = when (this) { ApiEventPayloadType.Authentication -> AUTHENTICATION ApiEventPayloadType.Consent -> CONSENT - ApiEventPayloadType.Enrolment -> ENROLMENT_V2 + ApiEventPayloadType.Enrolment -> ENROLMENT_V4 ApiEventPayloadType.Authorization -> AUTHORIZATION ApiEventPayloadType.FingerprintCapture -> FINGERPRINT_CAPTURE ApiEventPayloadType.OneToOneMatch -> ONE_TO_MANY_MATCH diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt index 362e81fc85..6125a39b4c 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt @@ -317,6 +317,20 @@ fun validateEnrolmentEventV2ApiModel(json: JSONObject) { } } +fun validateEnrolmentEventV4ApiModel(json: JSONObject) { + validateCommonParams(json, "Enrolment", 4) + + with(json.getJSONObject("payload")) { + validateTimestamp(getJSONObject("startTime")) + assertThat(getString("subjectId")).isNotNull() + assertThat(getString("projectId")).isNotNull() + assertThat(getString("moduleId")).isNotNull() + assertThat(getString("attendantId")).isNotNull() + assertThat(getJSONArray("biometricReferenceIds")).isNotNull() + assertThat(length()).isEqualTo(6) + } +} + fun validateIntentParsingEventApiModel(json: JSONObject) { validateCommonParams(json, "IntentParsing", 2) with(json.getJSONObject("payload")) { diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventToApiUseCaseTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventToApiUseCaseTest.kt index 2b7caddd6b..c35cbcdf72 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventToApiUseCaseTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/usecases/MapDomainEventToApiUseCaseTest.kt @@ -25,6 +25,7 @@ import com.simprints.infra.events.sampledata.createConsentEvent import com.simprints.infra.events.sampledata.createEnrolmentCallbackEvent import com.simprints.infra.events.sampledata.createEnrolmentCalloutEvent import com.simprints.infra.events.sampledata.createEnrolmentEventV2 +import com.simprints.infra.events.sampledata.createEnrolmentEventV4 import com.simprints.infra.events.sampledata.createEventDownSyncRequestEvent import com.simprints.infra.events.sampledata.createEventUpSyncRequestEvent import com.simprints.infra.events.sampledata.createFaceCaptureBiometricsEvent @@ -102,6 +103,7 @@ import com.simprints.infra.eventsync.event.validateConnectivitySnapshotEventApiM import com.simprints.infra.eventsync.event.validateConsentEventApiModel import com.simprints.infra.eventsync.event.validateDownSyncRequestEventApiModel import com.simprints.infra.eventsync.event.validateEnrolmentEventV2ApiModel +import com.simprints.infra.eventsync.event.validateEnrolmentEventV4ApiModel import com.simprints.infra.eventsync.event.validateFaceCaptureBiometricsEventApiModel import com.simprints.infra.eventsync.event.validateFaceCaptureConfirmationEventApiModel import com.simprints.infra.eventsync.event.validateFaceCaptureEventApiModel @@ -324,6 +326,15 @@ internal class MapDomainEventToApiUseCaseTest { validateEnrolmentEventV2ApiModel(json) } + @Test + fun validateEnrolmentV4_enrolmentEventApiModel() { + val event = createEnrolmentEventV4() + val apiEvent = useCase(event, project) + val json = JSONObject(jackson.writeValueAsString(apiEvent)) + + validateEnrolmentEventV4ApiModel(json) + } + @Test fun validate_completionCheckEventApiModel() { val event = createCompletionCheckEvent() diff --git a/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt b/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt index 50fcb1485d..3397129a0e 100644 --- a/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt +++ b/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt @@ -26,6 +26,7 @@ import com.simprints.infra.events.event.domain.models.ConsentEvent import com.simprints.infra.events.event.domain.models.ConsentEvent.ConsentPayload.Result.ACCEPTED import com.simprints.infra.events.event.domain.models.ConsentEvent.ConsentPayload.Type.INDIVIDUAL import com.simprints.infra.events.event.domain.models.EnrolmentEventV2 +import com.simprints.infra.events.event.domain.models.EnrolmentEventV4 import com.simprints.infra.events.event.domain.models.Event import com.simprints.infra.events.event.domain.models.EventType import com.simprints.infra.events.event.domain.models.FingerComparisonStrategy @@ -316,6 +317,15 @@ fun createEnrolmentEventV2() = EnrolmentEventV2( GUID2, ) +fun createEnrolmentEventV4() = EnrolmentEventV4( + CREATED_AT, + GUID1, + DEFAULT_PROJECT_ID, + DEFAULT_MODULE_ID, + DEFAULT_USER_ID, + listOf(GUID1, GUID2), +) + fun createFingerprintCaptureEvent() = FingerprintCaptureEvent( createdAt = CREATED_AT, endTime = ENDED_AT, diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV2.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV2.kt index 420f38dc26..a5c40372d8 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV2.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV2.kt @@ -8,6 +8,7 @@ import com.simprints.infra.events.event.domain.models.EventType.ENROLMENT_V2 import java.util.UUID @Keep +@Deprecated("Replaced by v4 in 2025.1.0") data class EnrolmentEventV2( override val id: String = UUID.randomUUID().toString(), override val payload: EnrolmentPayload, diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV4.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV4.kt new file mode 100644 index 0000000000..01cb57dc1f --- /dev/null +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV4.kt @@ -0,0 +1,69 @@ +package com.simprints.infra.events.event.domain.models + +import androidx.annotation.Keep +import com.simprints.core.domain.tokenization.TokenizableString +import com.simprints.core.tools.time.Timestamp +import com.simprints.infra.config.store.models.TokenKeyType +import com.simprints.infra.events.event.domain.models.EventType.ENROLMENT_V4 +import java.util.UUID + +@Keep +data class EnrolmentEventV4( + override val id: String = UUID.randomUUID().toString(), + override val payload: EnrolmentPayload, + override val type: EventType, + override var scopeId: String? = null, + override var projectId: String? = null, +) : Event() { + constructor( + createdAt: Timestamp, + subjectId: String, + projectId: String, + moduleId: TokenizableString, + attendantId: TokenizableString, + biometricReferenceIds: List, + ) : this( + UUID.randomUUID().toString(), + EnrolmentPayload( + createdAt = createdAt, + eventVersion = EVENT_VERSION, + subjectId = subjectId, + projectId = projectId, + moduleId = moduleId, + attendantId = attendantId, + biometricReferenceIds = biometricReferenceIds, + ), + ENROLMENT_V4, + ) + + override fun getTokenizableFields(): Map = mapOf( + TokenKeyType.AttendantId to payload.attendantId, + TokenKeyType.ModuleId to payload.moduleId, + ) + + override fun setTokenizedFields(map: Map) = this.copy( + payload = payload.copy( + attendantId = map[TokenKeyType.AttendantId] ?: payload.attendantId, + moduleId = map[TokenKeyType.ModuleId] ?: payload.moduleId, + ), + ) + + @Keep + data class EnrolmentPayload( + override val createdAt: Timestamp, + override val eventVersion: Int, + val subjectId: String, + val projectId: String, + val moduleId: TokenizableString, + val attendantId: TokenizableString, + val biometricReferenceIds: List, + override val endedAt: Timestamp? = null, + override val type: EventType = ENROLMENT_V4, + ) : EventPayload() { + override fun toSafeString(): String = "subject ID: $subjectId, module ID: $moduleId" + } + + companion object { + const val EVENT_VERSION = 4 + } +} diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/Event.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/Event.kt index 37799055c7..9c143ed770 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/Event.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/Event.kt @@ -26,6 +26,7 @@ import com.simprints.infra.events.event.domain.models.EventType.Companion.COMPLE import com.simprints.infra.events.event.domain.models.EventType.Companion.CONNECTIVITY_SNAPSHOT_KEY import com.simprints.infra.events.event.domain.models.EventType.Companion.CONSENT_KEY import com.simprints.infra.events.event.domain.models.EventType.Companion.ENROLMENT_V2_KEY +import com.simprints.infra.events.event.domain.models.EventType.Companion.ENROLMENT_V4_KEY import com.simprints.infra.events.event.domain.models.EventType.Companion.EVENT_DOWN_SYNC_REQUEST_KEY import com.simprints.infra.events.event.domain.models.EventType.Companion.EVENT_UP_SYNC_REQUEST_KEY import com.simprints.infra.events.event.domain.models.EventType.Companion.FACE_CAPTURE_BIOMETRICS_KEY @@ -114,6 +115,7 @@ import com.simprints.infra.events.event.domain.models.upsync.EventUpSyncRequestE JsonSubTypes.Type(value = ConnectivitySnapshotEvent::class, name = CONNECTIVITY_SNAPSHOT_KEY), JsonSubTypes.Type(value = ConsentEvent::class, name = CONSENT_KEY), JsonSubTypes.Type(value = EnrolmentEventV2::class, name = ENROLMENT_V2_KEY), + JsonSubTypes.Type(value = EnrolmentEventV4::class, name = ENROLMENT_V4_KEY), JsonSubTypes.Type(value = FingerprintCaptureEvent::class, name = FINGERPRINT_CAPTURE_KEY), JsonSubTypes.Type( value = FingerprintCaptureBiometricsEvent::class, diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventPayload.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventPayload.kt index 2c38afcda4..8018878ee1 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventPayload.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventPayload.kt @@ -72,6 +72,7 @@ import com.simprints.infra.events.event.domain.models.upsync.EventUpSyncRequestE JsonSubTypes.Type(value = ConnectivitySnapshotPayload::class, name = EventType.CONNECTIVITY_SNAPSHOT_KEY), JsonSubTypes.Type(value = ConsentPayload::class, name = EventType.CONSENT_KEY), JsonSubTypes.Type(value = EnrolmentEventV2.EnrolmentPayload::class, name = EventType.ENROLMENT_V2_KEY), + JsonSubTypes.Type(value = EnrolmentEventV4.EnrolmentPayload::class, name = EventType.ENROLMENT_V4_KEY), JsonSubTypes.Type(value = FingerprintCapturePayload::class, name = EventType.FINGERPRINT_CAPTURE_KEY), JsonSubTypes.Type(value = FingerprintCaptureBiometricsPayload::class, name = EventType.FINGERPRINT_CAPTURE_BIOMETRICS_KEY), JsonSubTypes.Type(value = GuidSelectionPayload::class, name = EventType.GUID_SELECTION_KEY), diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventType.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventType.kt index fad6d3113b..c67dd9f77d 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventType.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/EventType.kt @@ -14,9 +14,12 @@ enum class EventType { // key added: CONSENT_KEY CONSENT, - // key added: ENROLMENT_V1_KE2 + // key added: ENROLMENT_V2_KEY ENROLMENT_V2, + // key added: ENROLMENT_V4_KEY + ENROLMENT_V4, + // key added: AUTHORIZATION_KEY AUTHORIZATION, @@ -155,6 +158,7 @@ enum class EventType { const val AUTHENTICATION_KEY = "AUTHENTICATION" const val CONSENT_KEY = "CONSENT" const val ENROLMENT_V2_KEY = "ENROLMENT_V2" + const val ENROLMENT_V4_KEY = "ENROLMENT_V4" const val AUTHORIZATION_KEY = "AUTHORIZATION" const val FINGERPRINT_CAPTURE_KEY = "FINGERPRINT_CAPTURE" const val FINGERPRINT_CAPTURE_BIOMETRICS_KEY = "FINGERPRINT_CAPTURE_BIOMETRICS" diff --git a/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV4Test.kt b/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV4Test.kt new file mode 100644 index 0000000000..15c7bf54be --- /dev/null +++ b/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EnrolmentEventV4Test.kt @@ -0,0 +1,37 @@ +package com.simprints.infra.events.event.domain.models + +import com.google.common.truth.Truth.assertThat +import com.simprints.infra.events.event.domain.models.EventType.ENROLMENT_V4 +import com.simprints.infra.events.sampledata.SampleDefaults.CREATED_AT +import com.simprints.infra.events.sampledata.SampleDefaults.DEFAULT_MODULE_ID +import com.simprints.infra.events.sampledata.SampleDefaults.DEFAULT_PROJECT_ID +import com.simprints.infra.events.sampledata.SampleDefaults.DEFAULT_USER_ID +import com.simprints.infra.events.sampledata.SampleDefaults.GUID1 +import com.simprints.infra.events.sampledata.SampleDefaults.GUID2 +import org.junit.Test + +class EnrolmentEventV4Test { + @Test + fun create_EnrolmentEvent() { + val event = EnrolmentEventV4( + CREATED_AT, + GUID1, + DEFAULT_PROJECT_ID, + DEFAULT_MODULE_ID, + DEFAULT_USER_ID, + listOf(GUID2), + ) + + assertThat(event.id).isNotNull() + assertThat(event.type).isEqualTo(ENROLMENT_V4) + with(event.payload) { + assertThat(createdAt).isEqualTo(CREATED_AT) + assertThat(eventVersion).isEqualTo(EnrolmentEventV4.EVENT_VERSION) + assertThat(type).isEqualTo(ENROLMENT_V4) + assertThat(projectId).isEqualTo(DEFAULT_PROJECT_ID) + assertThat(moduleId).isEqualTo(DEFAULT_MODULE_ID) + assertThat(attendantId).isEqualTo(DEFAULT_USER_ID) + assertThat(biometricReferenceIds).containsExactly(GUID2) + } + } +} diff --git a/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EventPayloadTest.kt b/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EventPayloadTest.kt index e9df4113cd..8242590e7d 100644 --- a/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EventPayloadTest.kt +++ b/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EventPayloadTest.kt @@ -200,6 +200,14 @@ class EventPayloadTest { attendantId = DEFAULT_USER_ID, personCreationEventId = GUID2, ), + EnrolmentEventV4( + createdAt = CREATED_AT, + subjectId = GUID1, + projectId = DEFAULT_PROJECT_ID, + moduleId = DEFAULT_MODULE_ID, + attendantId = DEFAULT_USER_ID, + biometricReferenceIds = listOf(GUID1, GUID2), + ), GuidSelectionEvent(CREATED_AT, GUID1), IntentParsingEvent(CREATED_AT, COMMCARE), InvalidIntentEvent(CREATED_AT, "REGISTER", mapOf("extra_key" to "value")), diff --git a/infra/events/src/test/resources/all-events/enrolment_v4.json b/infra/events/src/test/resources/all-events/enrolment_v4.json new file mode 100644 index 0000000000..d4b3824cf1 --- /dev/null +++ b/infra/events/src/test/resources/all-events/enrolment_v4.json @@ -0,0 +1,28 @@ +{ + "id": "d93f16d6-0581-4f8b-8ddf-f3ae8ec3579e", + "type": "ENROLMENT_V4", + "labels": { + "projectId": "TEST6Oai41ps1pBNrzBL", + "sessionId": "e35c39f9-b81e-48f2-97e7-46ecc8399bb4", + "deviceId": "f2fd8393c0a0be67" + }, + "payload": { + "type": "ENROLMENT_V4", + "eventVersion": 4, + "createdAt": 178967890, + "endedAt": 0, + "subjectId": "9aa64aff-46b5-4ab4-90b5-82c0e6d9725f", + "projectId": "TEST6Oai41ps1pBNrzBL", + "moduleId": { + "className": "TokenizableString.Raw", + "value": "module1" + }, + "attendantId": { + "className": "TokenizableString.Raw", + "value": "user1" + }, + "biometricReferenceIds": [ + "94828488-47ce-4c76-8ace-33d69b2a6d75" + ] + } +} From 1abf5f4ef98d7b7504e1746ba55b809ca6b75b14 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Thu, 6 Feb 2025 16:50:57 +0200 Subject: [PATCH 05/22] MS-871 Track Enrolment v4 instead of v2 --- .../capture/screens/FaceCaptureViewModel.kt | 3 + .../usecases/SimpleCaptureEventReporter.kt | 15 +++++ .../screens/FaceCaptureViewModelTest.kt | 9 +++ .../SimpleCaptureEventReporterTest.kt | 26 +++++++-- .../screen/EnrolLastBiometricViewModel.kt | 14 ++--- .../screen/EnrolLastBiometricViewModelTest.kt | 26 ++++++--- .../usecases/response/EnrolSubjectUseCase.kt | 19 ++++--- .../response/EnrolSubjectUseCaseTest.kt | 44 ++++++++------- .../screen/FingerprintCaptureViewModel.kt | 10 ++++ ...BiometricReferenceCreationEventsUseCase.kt | 29 ++++++++++ .../screen/FingerprintCaptureViewModelTest.kt | 10 +++- ...etricReferenceCreationEventsUseCaseTest.kt | 56 +++++++++++++++++++ .../sync/up/tasks/EventUpSyncTask.kt | 2 + .../sync/up/tasks/EventUpSyncTaskTest.kt | 10 +++- .../validators/EnrolmentEventValidator.kt | 10 ++-- .../validators/EnrolmentEventValidatorTest.kt | 13 +++-- 16 files changed, 234 insertions(+), 62 deletions(-) create mode 100644 fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventsUseCase.kt create mode 100644 fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventsUseCaseTest.kt diff --git a/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt b/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt index 23e1082096..16aee324aa 100644 --- a/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt +++ b/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.simprints.core.DeviceID +import com.simprints.core.domain.common.faceReferenceId import com.simprints.core.livedata.LiveDataEvent import com.simprints.core.livedata.LiveDataEventWithContent import com.simprints.core.livedata.send @@ -184,6 +185,8 @@ internal class FaceCaptureViewModel @Inject constructor( ), ) } + val referenceId = items.mapNotNull { it.sample?.template }.faceReferenceId().orEmpty() + eventReporter.addBiometricReferenceCreationEvents(referenceId, items.mapNotNull { it.captureEventId }) _finishFlowEvent.send(FaceCaptureResult(items)) } diff --git a/face/capture/src/main/java/com/simprints/face/capture/usecases/SimpleCaptureEventReporter.kt b/face/capture/src/main/java/com/simprints/face/capture/usecases/SimpleCaptureEventReporter.kt index 806d586ee8..ab3c99bed6 100644 --- a/face/capture/src/main/java/com/simprints/face/capture/usecases/SimpleCaptureEventReporter.kt +++ b/face/capture/src/main/java/com/simprints/face/capture/usecases/SimpleCaptureEventReporter.kt @@ -5,6 +5,7 @@ import com.simprints.core.tools.time.TimeHelper import com.simprints.core.tools.time.Timestamp import com.simprints.core.tools.utils.EncodingUtils import com.simprints.face.capture.models.FaceDetection +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent import com.simprints.infra.events.event.domain.models.face.FaceCaptureBiometricsEvent import com.simprints.infra.events.event.domain.models.face.FaceCaptureConfirmationEvent import com.simprints.infra.events.event.domain.models.face.FaceCaptureConfirmationEvent.FaceCaptureConfirmationPayload.Result @@ -100,4 +101,18 @@ internal class SimpleCaptureEventReporter @Inject constructor( it.format, ) }!! + + fun addBiometricReferenceCreationEvents( + referenceId: String, + captureIds: List, + ) = sessionCoroutineScope.launch { + eventRepository.addOrUpdateEvent( + BiometricReferenceCreationEvent( + startTime = timeHelper.now(), + referenceId = referenceId, + modality = BiometricReferenceCreationEvent.BiometricReferenceModality.FACE, + captureIds = captureIds, + ), + ) + } } diff --git a/face/capture/src/test/java/com/simprints/face/capture/screens/FaceCaptureViewModelTest.kt b/face/capture/src/test/java/com/simprints/face/capture/screens/FaceCaptureViewModelTest.kt index 0e042b5535..65d011f544 100644 --- a/face/capture/src/test/java/com/simprints/face/capture/screens/FaceCaptureViewModelTest.kt +++ b/face/capture/src/test/java/com/simprints/face/capture/screens/FaceCaptureViewModelTest.kt @@ -122,6 +122,15 @@ class FaceCaptureViewModelTest { coVerify(atLeast = 1) { faceImageUseCase.invoke(any(), any()) } } + @Test + fun `Save biometric reference creation when flow finishes`() { + viewModel.captureFinished(faceDetections) + viewModel.flowFinished() + coVerify(atLeast = 1) { + eventReporter.addBiometricReferenceCreationEvents(any(), any()) + } + } + @Test fun `Recapture requests clears capture list`() { viewModel.captureFinished(faceDetections) diff --git a/face/capture/src/test/java/com/simprints/face/capture/usecases/SimpleCaptureEventReporterTest.kt b/face/capture/src/test/java/com/simprints/face/capture/usecases/SimpleCaptureEventReporterTest.kt index fa8d20f3b0..4465a5f930 100644 --- a/face/capture/src/test/java/com/simprints/face/capture/usecases/SimpleCaptureEventReporterTest.kt +++ b/face/capture/src/test/java/com/simprints/face/capture/usecases/SimpleCaptureEventReporterTest.kt @@ -7,6 +7,8 @@ import com.simprints.core.tools.time.Timestamp import com.simprints.core.tools.utils.EncodingUtils import com.simprints.face.capture.models.FaceDetection import com.simprints.face.infra.basebiosdk.detection.Face +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent.BiometricReferenceCreationPayload import com.simprints.infra.events.event.domain.models.face.FaceCaptureBiometricsEvent import com.simprints.infra.events.event.domain.models.face.FaceCaptureConfirmationEvent import com.simprints.infra.events.event.domain.models.face.FaceCaptureEvent @@ -73,8 +75,9 @@ class SimpleCaptureEventReporterTest { eventRepository.addOrUpdateEvent( withArg { assertThat(it).isInstanceOf(FaceCaptureConfirmationEvent::class.java) - assertThat((it.payload as FaceCaptureConfirmationEvent.FaceCaptureConfirmationPayload).result) - .isEqualTo(FaceCaptureConfirmationEvent.FaceCaptureConfirmationPayload.Result.CONTINUE) + assertThat((it.payload as FaceCaptureConfirmationEvent.FaceCaptureConfirmationPayload).result).isEqualTo( + FaceCaptureConfirmationEvent.FaceCaptureConfirmationPayload.Result.CONTINUE, + ) }, ) } @@ -87,8 +90,9 @@ class SimpleCaptureEventReporterTest { eventRepository.addOrUpdateEvent( withArg { assertThat(it).isInstanceOf(FaceCaptureConfirmationEvent::class.java) - assertThat((it.payload as FaceCaptureConfirmationEvent.FaceCaptureConfirmationPayload).result) - .isEqualTo(FaceCaptureConfirmationEvent.FaceCaptureConfirmationPayload.Result.RECAPTURE) + assertThat((it.payload as FaceCaptureConfirmationEvent.FaceCaptureConfirmationPayload).result).isEqualTo( + FaceCaptureConfirmationEvent.FaceCaptureConfirmationPayload.Result.RECAPTURE, + ) }, ) } @@ -182,6 +186,20 @@ class SimpleCaptureEventReporterTest { } } + @Test + fun `Adds reference creation event`() = runTest { + reporter.addBiometricReferenceCreationEvents("id", listOf("id1", "id2", "id3")) + coVerify { + eventRepository.addOrUpdateEvent( + withArg { + assertThat(it).isInstanceOf(BiometricReferenceCreationEvent::class.java) + assertThat((it.payload as BiometricReferenceCreationPayload).modality) + .isEqualTo(BiometricReferenceCreationEvent.BiometricReferenceModality.FACE) + }, + ) + } + } + private fun getDetection(status: FaceDetection.Status) = FaceDetection(mockk(), getFace(), status, mockk(), Timestamp(1L), false, "id", Timestamp(1L)) diff --git a/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt b/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt index 0e26782c12..c34ca748df 100644 --- a/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt +++ b/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt @@ -17,8 +17,8 @@ import com.simprints.infra.config.sync.ConfigManager import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository import com.simprints.infra.enrolment.records.repository.domain.models.Subject import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction -import com.simprints.infra.events.event.domain.models.EnrolmentEventV2 -import com.simprints.infra.events.event.domain.models.PersonCreationEvent +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent +import com.simprints.infra.events.event.domain.models.EnrolmentEventV4 import com.simprints.infra.events.session.SessionEventRepository import com.simprints.infra.logging.LoggingConstants.CrashReportTag.ENROLMENT import com.simprints.infra.logging.Simber @@ -85,20 +85,20 @@ internal class EnrolLastBiometricViewModel @Inject constructor( private suspend fun registerEvent(subject: Subject) { Simber.d("Register events for enrolments", tag = ENROLMENT) - val personCreationEvent = eventRepository + val biometricReferenceIds = eventRepository .getEventsInCurrentSession() - .filterIsInstance() + .filterIsInstance() .sortedByDescending { it.payload.createdAt } - .first() + .map { it.payload.id } eventRepository.addOrUpdateEvent( - EnrolmentEventV2( + EnrolmentEventV4( timeHelper.now(), subject.subjectId, subject.projectId, subject.moduleId, subject.attendantId, - personCreationEvent.id, + biometricReferenceIds, ), ) } diff --git a/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt b/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt index 34affcfd60..083269c4bb 100644 --- a/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt +++ b/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt @@ -14,9 +14,10 @@ import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.config.sync.ConfigManager import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository import com.simprints.infra.enrolment.records.repository.domain.models.Subject -import com.simprints.infra.events.event.domain.models.EnrolmentEventV2 +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent.BiometricReferenceCreationPayload +import com.simprints.infra.events.event.domain.models.EnrolmentEventV4 import com.simprints.infra.events.event.domain.models.PersonCreationEvent -import com.simprints.infra.events.event.domain.models.PersonCreationEvent.PersonCreationPayload import com.simprints.infra.events.session.SessionEventRepository import com.simprints.testtools.common.coroutines.TestCoroutineRule import io.mockk.MockKAnnotations @@ -213,30 +214,37 @@ internal class EnrolLastBiometricViewModelTest { } @Test - fun `Uses latest PersonCreationEvent for Enrolment event`() = runTest { - val personCreationEvent1 = mockk { + fun `Uses all PersonCreationEvents for Enrolment event`() = runTest { + val personCreationEvent1 = mockk { every { id } returns "personCreationEventId1" - every { payload } returns mockk { + every { payload } returns mockk { every { createdAt } returns Timestamp(1) + every { id } returns "referenceId1" } } val personCreationId2 = "personCreationEventId2" - val personCreationEvent2 = mockk { + val personCreationEvent2 = mockk { every { id } returns personCreationId2 - every { payload } returns mockk { + every { payload } returns mockk { every { createdAt } returns Timestamp(2) + every { id } returns "referenceId2" } } + coEvery { eventRepository.getEventsInCurrentSession() } returns listOf( - personCreationEvent1, personCreationEvent2, + personCreationEvent1, ) viewModel.enrolBiometric(createParams(listOf())) coVerify { eventRepository.addOrUpdateEvent( - match { it is EnrolmentEventV2 && it.payload.personCreationEventId == personCreationId2 }, + withArg { + assertThat(it).isInstanceOf(EnrolmentEventV4::class.java) + assertThat((it.payload as EnrolmentEventV4.EnrolmentPayload).biometricReferenceIds) + .containsExactly("referenceId1", "referenceId2") + }, ) } } diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCase.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCase.kt index f0522293d9..7f318cf907 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCase.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCase.kt @@ -5,8 +5,8 @@ import com.simprints.infra.config.store.models.Project import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository import com.simprints.infra.enrolment.records.repository.domain.models.Subject import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction -import com.simprints.infra.events.event.domain.models.EnrolmentEventV2 -import com.simprints.infra.events.event.domain.models.PersonCreationEvent +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent +import com.simprints.infra.events.event.domain.models.EnrolmentEventV4 import com.simprints.infra.events.session.SessionEventRepository import javax.inject.Inject @@ -15,21 +15,24 @@ internal class EnrolSubjectUseCase @Inject constructor( private val timeHelper: TimeHelper, private val enrolmentRecordRepository: EnrolmentRecordRepository, ) { - suspend operator fun invoke(subject: Subject, project: Project) { - val personCreationEvent = eventRepository + suspend operator fun invoke( + subject: Subject, + project: Project, + ) { + val biometricReferenceIds = eventRepository .getEventsInCurrentSession() - .filterIsInstance() + .filterIsInstance() .sortedByDescending { it.payload.createdAt } - .first() + .map { it.payload.id } eventRepository.addOrUpdateEvent( - EnrolmentEventV2( + EnrolmentEventV4( timeHelper.now(), subject.subjectId, subject.projectId, subject.moduleId, subject.attendantId, - personCreationEvent.id, + biometricReferenceIds, ), ) enrolmentRecordRepository.performActions(listOf(SubjectAction.Creation(subject)), project) diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt index d3a101c468..eec3751e28 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt @@ -8,9 +8,10 @@ import com.simprints.infra.config.store.models.Project import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository import com.simprints.infra.enrolment.records.repository.domain.models.Subject import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction -import com.simprints.infra.events.event.domain.models.EnrolmentEventV2 +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent.BiometricReferenceCreationPayload +import com.simprints.infra.events.event.domain.models.EnrolmentEventV4 import com.simprints.infra.events.event.domain.models.PersonCreationEvent -import com.simprints.infra.events.event.domain.models.PersonCreationEvent.PersonCreationPayload import com.simprints.infra.events.session.SessionEventRepository import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -53,9 +54,11 @@ class EnrolSubjectUseCaseTest { } @Test - fun `Adds enrolment V2 event when called`() = runTest { + fun `Adds enrolment V4 event when called`() = runTest { coEvery { eventRepository.getEventsInCurrentSession() } returns listOf( - mockk { every { id } returns "personCreationId" }, + mockk { + every { payload.id } returns "referenceId" + }, ) useCase.invoke( @@ -65,13 +68,13 @@ class EnrolSubjectUseCaseTest { attendantId = "moduleId".asTokenizableRaw(), moduleId = "attendantId".asTokenizableRaw(), ), - project + project, ) coVerify { eventRepository.addOrUpdateEvent( withArg { - assertThat(it).isInstanceOf(EnrolmentEventV2::class.java) + assertThat(it).isInstanceOf(EnrolmentEventV4::class.java) }, ) } @@ -90,7 +93,7 @@ class EnrolSubjectUseCaseTest { attendantId = "moduleId".asTokenizableRaw(), moduleId = "attendantId".asTokenizableRaw(), ), - project + project, ) coVerify { @@ -98,29 +101,28 @@ class EnrolSubjectUseCaseTest { withArg { assertThat(it.first()).isInstanceOf(SubjectAction.Creation::class.java) }, - project + project, ) } } @Test - fun `Uses latest PersonCreationEvent`() = runTest { - val personCreationEvent1 = mockk { - every { id } returns "personCreationEventId1" - every { payload } returns mockk { + fun `Uses latest all BiometricReferenceCreationEvent`() = runTest { + val personCreationEvent1 = mockk { + every { payload } returns mockk { every { createdAt } returns Timestamp(1) + every { id } returns "referenceId1" } } - val personCreationId2 = "personCreationEventId2" - val personCreationEvent2 = mockk { - every { id } returns personCreationId2 - every { payload } returns mockk { + val personCreationEvent2 = mockk { + every { payload } returns mockk { every { createdAt } returns Timestamp(2) + every { id } returns "referenceId2" } } coEvery { eventRepository.getEventsInCurrentSession() } returns listOf( - personCreationEvent1, personCreationEvent2, + personCreationEvent1, ) useCase.invoke( @@ -130,12 +132,16 @@ class EnrolSubjectUseCaseTest { attendantId = "moduleId".asTokenizableRaw(), moduleId = "attendantId".asTokenizableRaw(), ), - project + project, ) coVerify { eventRepository.addOrUpdateEvent( - match { it is EnrolmentEventV2 && it.payload.personCreationEventId == personCreationId2 }, + withArg { + assertThat(it).isInstanceOf(EnrolmentEventV4::class.java) + assertThat((it as EnrolmentEventV4).payload.biometricReferenceIds) + .containsExactly("referenceId1", "referenceId2") + }, ) } } diff --git a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt index cce8bb0df2..61b52cb051 100644 --- a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt +++ b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.simprints.core.ExternalScope +import com.simprints.core.domain.common.fingerprintReferenceId import com.simprints.core.domain.fingerprint.IFingerIdentifier import com.simprints.core.livedata.LiveDataEvent import com.simprints.core.livedata.LiveDataEventWithContent @@ -24,6 +25,7 @@ import com.simprints.fingerprint.capture.state.CollectFingerprintsState import com.simprints.fingerprint.capture.state.FingerState import com.simprints.fingerprint.capture.state.LiveFeedbackState import com.simprints.fingerprint.capture.state.ScanResult +import com.simprints.fingerprint.capture.usecase.AddBiometricReferenceCreationEventsUseCase import com.simprints.fingerprint.capture.usecase.AddCaptureEventsUseCase import com.simprints.fingerprint.capture.usecase.GetNextFingerToAddUseCase import com.simprints.fingerprint.capture.usecase.GetStartStateUseCase @@ -74,6 +76,7 @@ internal class FingerprintCaptureViewModel @Inject constructor( private val getNextFingerToAdd: GetNextFingerToAddUseCase, private val getStartState: GetStartStateUseCase, private val addCaptureEvents: AddCaptureEventsUseCase, + private val addBiometricReferenceCreatedEvents: AddBiometricReferenceCreationEventsUseCase, private val tracker: FingerprintScanningStatusTracker, private val isNoFingerDetectedLimitReachedUseCase: IsNoFingerDetectedLimitReachedUseCase, @ExternalScope private val externalScope: CoroutineScope, @@ -669,6 +672,13 @@ internal class FingerprintCaptureViewModel @Inject constructor( ), ) } + val biometricReferenceId = resultItems + .mapNotNull { it.sample } + .map { it.templateQualityScore to it.template } + .fingerprintReferenceId() + .orEmpty() + addBiometricReferenceCreatedEvents(biometricReferenceId, resultItems.mapNotNull { it.captureEventId }) + _finishWithFingerprints.send(FingerprintCaptureResult(resultItems)) } diff --git a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventsUseCase.kt b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventsUseCase.kt new file mode 100644 index 0000000000..cee0a30b6a --- /dev/null +++ b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventsUseCase.kt @@ -0,0 +1,29 @@ +package com.simprints.fingerprint.capture.usecase + +import com.simprints.core.SessionCoroutineScope +import com.simprints.core.tools.time.TimeHelper +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent +import com.simprints.infra.events.session.SessionEventRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import javax.inject.Inject + +internal class AddBiometricReferenceCreationEventsUseCase @Inject constructor( + private val timeHelper: TimeHelper, + private val eventRepository: SessionEventRepository, + @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, +) { + operator fun invoke( + referenceId: String, + captureIds: List, + ) = sessionCoroutineScope.launch { + eventRepository.addOrUpdateEvent( + BiometricReferenceCreationEvent( + startTime = timeHelper.now(), + referenceId = referenceId, + modality = BiometricReferenceCreationEvent.BiometricReferenceModality.FINGERPRINT, + captureIds = captureIds, + ), + ) + } +} diff --git a/fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModelTest.kt b/fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModelTest.kt index a9efe989e4..8fdbf9f4f7 100644 --- a/fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModelTest.kt +++ b/fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModelTest.kt @@ -16,6 +16,7 @@ import com.simprints.fingerprint.capture.state.CollectFingerprintsState import com.simprints.fingerprint.capture.state.FingerState import com.simprints.fingerprint.capture.state.LiveFeedbackState import com.simprints.fingerprint.capture.state.ScanResult +import com.simprints.fingerprint.capture.usecase.AddBiometricReferenceCreationEventsUseCase import com.simprints.fingerprint.capture.usecase.AddCaptureEventsUseCase import com.simprints.fingerprint.capture.usecase.GetNextFingerToAddUseCase import com.simprints.fingerprint.capture.usecase.GetStartStateUseCase @@ -103,6 +104,9 @@ class FingerprintCaptureViewModelTest { @MockK private lateinit var addCaptureEventsUseCase: AddCaptureEventsUseCase + @MockK + private lateinit var addBiometricReferenceCreatedEvents: AddBiometricReferenceCreationEventsUseCase + @MockK private lateinit var isNoFingerDetectedLimitReachedUseCase: IsNoFingerDetectedLimitReachedUseCase @@ -153,6 +157,7 @@ class FingerprintCaptureViewModelTest { getNextFingerToAdd = getNextFingerToAddUseCase, getStartState = getStartStateUseCase, addCaptureEvents = addCaptureEventsUseCase, + addBiometricReferenceCreatedEvents = addBiometricReferenceCreatedEvents, tracker = tracker, isNoFingerDetectedLimitReachedUseCase = isNoFingerDetectedLimitReachedUseCase, externalScope = CoroutineScope(testCoroutineRule.testCoroutineDispatcher), @@ -204,7 +209,7 @@ class FingerprintCaptureViewModelTest { } @Test - fun `test scanner supports image transfer then isImageTransferRequired should be equal to scanningTimeoutMs + imageTransferTimeoutMs`() = + fun `test scanner supports image transfer then isImageTransferRequired should be scanningTimeoutMs + imageTransferTimeoutMs`() = runTest { withImageTransfer() every { scanner.isImageTransferSupported() } returns true @@ -515,6 +520,7 @@ class FingerprintCaptureViewModelTest { vm.handleConfirmFingerprintsAndContinue() coVerify(exactly = 4) { saveImageUseCase.invoke(any(), any(), any(), any()) } + coVerify { addBiometricReferenceCreatedEvents.invoke(any(), any()) } vm.finishWithFingerprints.assertEventReceivedWithContentAssertions { actualFingerprints -> assertThat(actualFingerprints?.results).hasSize(FOUR_FINGERS_IDS.size) assertThat(actualFingerprints?.results?.map { it.identifier }).containsExactlyElementsIn( @@ -572,6 +578,7 @@ class FingerprintCaptureViewModelTest { vm.handleConfirmFingerprintsAndContinue() coVerify(exactly = 2) { saveImageUseCase.invoke(any(), any(), any(), any()) } + coVerify { addBiometricReferenceCreatedEvents.invoke(any(), any()) } vm.finishWithFingerprints.assertEventReceivedWithContentAssertions { actualFingerprints -> assertThat(actualFingerprints?.results).hasSize(TWO_FINGERS_IDS.size) @@ -628,6 +635,7 @@ class FingerprintCaptureViewModelTest { coVerify(exactly = 2) { addCaptureEventsUseCase.invoke(any(), any(), any(), any()) } vm.handleConfirmFingerprintsAndContinue() coVerify(exactly = 0) { saveImageUseCase.invoke(any(), any(), any(), any()) } + coVerify { addBiometricReferenceCreatedEvents.invoke(any(), any()) } vm.finishWithFingerprints.assertEventReceivedWithContentAssertions { actualFingerprints -> assertThat(actualFingerprints?.results).hasSize(TWO_FINGERS_IDS.size) diff --git a/fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventsUseCaseTest.kt b/fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventsUseCaseTest.kt new file mode 100644 index 0000000000..6e44237307 --- /dev/null +++ b/fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventsUseCaseTest.kt @@ -0,0 +1,56 @@ +package com.simprints.fingerprint.capture.usecase + +import com.google.common.truth.Truth.assertThat +import com.simprints.core.tools.time.TimeHelper +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent +import com.simprints.infra.events.session.SessionEventRepository +import com.simprints.testtools.common.coroutines.TestCoroutineRule +import io.mockk.MockKAnnotations +import io.mockk.coJustRun +import io.mockk.coVerify +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +internal class AddBiometricReferenceCreationEventsUseCaseTest { + @get:Rule + val testCoroutineRule = TestCoroutineRule() + + @MockK + lateinit var timeHelper: TimeHelper + + @MockK + lateinit var eventRepo: SessionEventRepository + + private lateinit var useCase: AddBiometricReferenceCreationEventsUseCase + + @Before + fun setUp() { + MockKAnnotations.init(this, relaxed = true) + + coJustRun { eventRepo.addOrUpdateEvent(any()) } + + useCase = AddBiometricReferenceCreationEventsUseCase( + timeHelper, + eventRepo, + CoroutineScope(testCoroutineRule.testCoroutineDispatcher), + ) + } + + @Test + fun `Adds reference creation event`() = runTest { + useCase("id", listOf("id1", "id2", "id3")) + coVerify { + eventRepo.addOrUpdateEvent( + withArg { + assertThat(it).isInstanceOf(BiometricReferenceCreationEvent::class.java) + assertThat((it.payload as BiometricReferenceCreationEvent.BiometricReferenceCreationPayload).modality) + .isEqualTo(BiometricReferenceCreationEvent.BiometricReferenceModality.FINGERPRINT) + }, + ) + } + } +} diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt index 4b7bce4ea3..34152a81bb 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt @@ -15,6 +15,7 @@ import com.simprints.infra.config.store.models.canSyncBiometricDataToSimprints import com.simprints.infra.config.sync.ConfigManager import com.simprints.infra.events.EventRepository import com.simprints.infra.events.event.domain.models.EnrolmentEventV2 +import com.simprints.infra.events.event.domain.models.EnrolmentEventV4 import com.simprints.infra.events.event.domain.models.Event import com.simprints.infra.events.event.domain.models.PersonCreationEvent import com.simprints.infra.events.event.domain.models.face.FaceCaptureBiometricsEvent @@ -315,6 +316,7 @@ internal class EventUpSyncTask @Inject constructor( config.canSyncBiometricDataToSimprints() -> events.filter { it is EnrolmentEventV2 || + it is EnrolmentEventV4 || it is PersonCreationEvent || it is FingerprintCaptureBiometricsEvent || it is FaceCaptureBiometricsEvent diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt index da687fc4b3..ebc9c36b3d 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt @@ -26,6 +26,7 @@ import com.simprints.infra.events.sampledata.SampleDefaults.GUID3 import com.simprints.infra.events.sampledata.createAlertScreenEvent import com.simprints.infra.events.sampledata.createAuthenticationEvent import com.simprints.infra.events.sampledata.createEnrolmentEventV2 +import com.simprints.infra.events.sampledata.createEnrolmentEventV4 import com.simprints.infra.events.sampledata.createEventWithSessionId import com.simprints.infra.events.sampledata.createFaceCaptureBiometricsEvent import com.simprints.infra.events.sampledata.createFingerprintCaptureBiometricsEvent @@ -118,7 +119,7 @@ internal class EventUpSyncTaskTest { timeHelper = timeHelper, configManager = configManager, jsonHelper = JsonHelper, - mapDomainEventScopeToApiUseCase = mapDomainEventScopeToApiUseCase + mapDomainEventScopeToApiUseCase = mapDomainEventScopeToApiUseCase, ) } @@ -266,6 +267,7 @@ internal class EventUpSyncTaskTest { createAlertScreenEvent(), // only following should be uploaded createEnrolmentEventV2(), + createEnrolmentEventV4(), createPersonCreationEvent(), createFingerprintCaptureBiometricsEvent(), createFaceCaptureBiometricsEvent(), @@ -277,7 +279,7 @@ internal class EventUpSyncTaskTest { coVerify(exactly = 1) { mapDomainEventScopeToApiUseCase(any(), capture(capturedRequest), any()) } - assertThat(capturedRequest.captured).hasSize(4) + assertThat(capturedRequest.captured).hasSize(5) } @Test @@ -295,6 +297,7 @@ internal class EventUpSyncTaskTest { // only following should be uploaded createPersonCreationEvent(), createEnrolmentEventV2(), + createEnrolmentEventV4(), createAlertScreenEvent(), ) @@ -304,7 +307,7 @@ internal class EventUpSyncTaskTest { coVerify(exactly = 1) { mapDomainEventScopeToApiUseCase(any(), capture(capturedRequest), any()) } - assertThat(capturedRequest.captured).hasSize(3) + assertThat(capturedRequest.captured).hasSize(4) } @Test @@ -354,6 +357,7 @@ internal class EventUpSyncTaskTest { ) coEvery { eventRepo.getEventsFromScope(GUID2) } returns listOf( createEnrolmentEventV2(), + createEnrolmentEventV4(), createAlertScreenEvent(), ) diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidator.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidator.kt index 7dea5d5a51..0ec947a576 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidator.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidator.kt @@ -1,8 +1,8 @@ package com.simprints.infra.events.event.domain.validators -import com.simprints.infra.events.event.domain.models.EnrolmentEventV2 +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent +import com.simprints.infra.events.event.domain.models.EnrolmentEventV4 import com.simprints.infra.events.event.domain.models.Event -import com.simprints.infra.events.event.domain.models.PersonCreationEvent import com.simprints.infra.events.event.domain.models.face.FaceCaptureEvent import com.simprints.infra.events.event.domain.models.fingerprint.FingerprintCaptureEvent import com.simprints.infra.events.exceptions.validator.EnrolmentEventValidatorException @@ -16,16 +16,16 @@ internal class EnrolmentEventValidator : EventValidator { currentEvents: List, eventToAdd: Event, ) { - if (eventToAdd is EnrolmentEventV2) { + if (eventToAdd is EnrolmentEventV4) { val hasFingerprint = currentEvents.any { it is FingerprintCaptureEvent } val hasFace = currentEvents.any { it is FaceCaptureEvent } - val hasPersonCreation = currentEvents.any { it is PersonCreationEvent } + val hasBiometricReference = currentEvents.any { it is BiometricReferenceCreationEvent } if (!hasFingerprint && !hasFace) { throw EnrolmentEventValidatorException("Missing fingerprint or face capture event") } - if (!hasPersonCreation) { + if (!hasBiometricReference) { throw EnrolmentEventValidatorException("Missing person creation event") } } diff --git a/infra/events/src/test/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidatorTest.kt b/infra/events/src/test/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidatorTest.kt index 49310b3b91..0f3b5df45f 100644 --- a/infra/events/src/test/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidatorTest.kt +++ b/infra/events/src/test/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidatorTest.kt @@ -2,7 +2,8 @@ package com.simprints.infra.events.event.domain.validators import com.simprints.infra.events.exceptions.validator.EnrolmentEventValidatorException import com.simprints.infra.events.sampledata.SampleDefaults.GUID1 -import com.simprints.infra.events.sampledata.createEnrolmentEventV2 +import com.simprints.infra.events.sampledata.createBiometricReferenceCreationEvent +import com.simprints.infra.events.sampledata.createEnrolmentEventV4 import com.simprints.infra.events.sampledata.createEventWithSessionId import com.simprints.infra.events.sampledata.createFaceCaptureEvent import com.simprints.infra.events.sampledata.createFingerprintCaptureEvent @@ -22,22 +23,22 @@ internal class EnrolmentEventValidatorTest { @Test fun validate_shouldValidateIfBiometricCaptureAndPersonCreationIsPresent() { val currentEvents = listOf(createFaceCaptureEvent(), createPersonCreationEvent()) - validator.run { validate(currentEvents, createEnrolmentEventV2()) } + validator.run { validate(currentEvents, createBiometricReferenceCreationEvent()) } } @Test fun validate_shouldThrowIfBiometricCaptureIsNotPresent() { assertThrows { - val currentEvents = listOf(createEventWithSessionId(GUID1, GUID1), createPersonCreationEvent()) - validator.validate(currentEvents, createEnrolmentEventV2()) + val currentEvents = listOf(createEventWithSessionId(GUID1, GUID1), createBiometricReferenceCreationEvent()) + validator.validate(currentEvents, createEnrolmentEventV4()) } } @Test - fun validate_shouldThrowIfPersonCreationIsNotPresent() { + fun validate_shouldThrowIfBiometricCreationEventIsNotPresent() { assertThrows { val currentEvents = listOf(createFingerprintCaptureEvent()) - validator.validate(currentEvents, createEnrolmentEventV2()) + validator.validate(currentEvents, createEnrolmentEventV4()) } } } From 9befa316e135edfa517af77200860d4c87f2a86f Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Thu, 6 Feb 2025 17:13:11 +0200 Subject: [PATCH 06/22] MS-871 Route probe biometric reference ID from capture to matcher --- .../face/capture/FaceCaptureResult.kt | 1 + .../capture/screens/FaceCaptureViewModel.kt | 2 +- .../com/simprints/matcher/MatchContract.kt | 2 ++ .../java/com/simprints/matcher/MatchParams.kt | 1 + .../matcher/usecases/SaveMatchEventUseCase.kt | 4 +++ .../matcher/screen/MatchViewModelTest.kt | 3 ++ .../usecases/FaceMatcherUseCaseTest.kt | 3 ++ .../usecases/FingerprintMatcherUseCaseTest.kt | 3 ++ .../usecases/SaveMatchEventUseCaseTest.kt | 9 +++++ .../orchestrator/OrchestratorViewModel.kt | 4 +-- .../steps/MatchStepStubPayload.kt | 12 +++++-- .../orchestrator/OrchestratorViewModelTest.kt | 8 ++--- .../cache/OrchestratorCacheIntegrationTest.kt | 1 + .../usecases/CreatePersonEventUseCaseTest.kt | 10 +++--- .../MapRefusalOrErrorResultUseCaseTest.kt | 2 +- ...apStepsForLastBiometricEnrolUseCaseTest.kt | 2 ++ .../usecases/ShouldCreatePersonUseCaseTest.kt | 34 +++++++++---------- .../CreateEnrolResponseUseCaseTest.kt | 4 +-- .../capture/FingerprintCaptureResult.kt | 1 + .../screen/FingerprintCaptureViewModel.kt | 2 +- .../sync/down/tasks/SubjectFactoryTest.kt | 2 ++ 21 files changed, 75 insertions(+), 35 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 5b49d08657..a5a029f611 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 @@ -6,6 +6,7 @@ import java.io.Serializable @Keep data class FaceCaptureResult( + val referenceId: String, val results: List, ) : Serializable { @Keep diff --git a/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt b/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt index 16aee324aa..b4007b7cde 100644 --- a/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt +++ b/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt @@ -188,7 +188,7 @@ internal class FaceCaptureViewModel @Inject constructor( val referenceId = items.mapNotNull { it.sample?.template }.faceReferenceId().orEmpty() eventReporter.addBiometricReferenceCreationEvents(referenceId, items.mapNotNull { it.captureEventId }) - _finishFlowEvent.send(FaceCaptureResult(items)) + _finishFlowEvent.send(FaceCaptureResult(referenceId, items)) } } diff --git a/feature/matcher/src/main/java/com/simprints/matcher/MatchContract.kt b/feature/matcher/src/main/java/com/simprints/matcher/MatchContract.kt index ef9d263025..b63b8ad98e 100644 --- a/feature/matcher/src/main/java/com/simprints/matcher/MatchContract.kt +++ b/feature/matcher/src/main/java/com/simprints/matcher/MatchContract.kt @@ -10,6 +10,7 @@ object MatchContract { val DESTINATION = R.id.matcherFragment fun getArgs( + referenceId: String = "", fingerprintSamples: List = emptyList(), faceSamples: List = emptyList(), fingerprintSDK: FingerprintConfiguration.BioSdk? = null, @@ -18,6 +19,7 @@ object MatchContract { biometricDataSource: BiometricDataSource, ) = MatchFragmentArgs( MatchParams( + referenceId, faceSamples, fingerprintSamples, fingerprintSDK, diff --git a/feature/matcher/src/main/java/com/simprints/matcher/MatchParams.kt b/feature/matcher/src/main/java/com/simprints/matcher/MatchParams.kt index 497ad40088..110754051a 100644 --- a/feature/matcher/src/main/java/com/simprints/matcher/MatchParams.kt +++ b/feature/matcher/src/main/java/com/simprints/matcher/MatchParams.kt @@ -13,6 +13,7 @@ import kotlinx.parcelize.Parcelize @Keep @Parcelize data class MatchParams( + val probeReferenceId: String, val probeFaceSamples: List = emptyList(), val probeFingerprintSamples: List = emptyList(), val fingerprintSDK: FingerprintConfiguration.BioSdk? = null, diff --git a/feature/matcher/src/main/java/com/simprints/matcher/usecases/SaveMatchEventUseCase.kt b/feature/matcher/src/main/java/com/simprints/matcher/usecases/SaveMatchEventUseCase.kt index 24c65d4830..cdd54f0154 100644 --- a/feature/matcher/src/main/java/com/simprints/matcher/usecases/SaveMatchEventUseCase.kt +++ b/feature/matcher/src/main/java/com/simprints/matcher/usecases/SaveMatchEventUseCase.kt @@ -41,6 +41,7 @@ internal class SaveMatchEventUseCase @Inject constructor( matchParams.queryForCandidates, matchEntries.firstOrNull(), if (matchParams.isFaceMatch()) null else getFingerprintComparisonStrategy(matchParams.fingerprintSDK!!), + matchParams.probeReferenceId, ) } else { getOneToManyEvent( @@ -50,6 +51,7 @@ internal class SaveMatchEventUseCase @Inject constructor( matchParams.queryForCandidates, candidatesCount, matchEntries, + matchParams.probeReferenceId, ) } eventRepository.addOrUpdateEvent(event) @@ -75,6 +77,7 @@ internal class SaveMatchEventUseCase @Inject constructor( queryForCandidates: SubjectQuery, matchEntry: MatchEntry?, fingerComparisonStrategy: FingerComparisonStrategy?, + biometricReferenceId: String, // TODO add field to event ) = OneToOneMatchEvent( startTime, endTime, @@ -91,6 +94,7 @@ internal class SaveMatchEventUseCase @Inject constructor( queryForCandidates: SubjectQuery, candidatesCount: Int, matchEntries: List, + biometricReferenceId: String, // TODO add field to event ) = OneToManyMatchEvent( startTime, endTime, diff --git a/feature/matcher/src/test/java/com/simprints/matcher/screen/MatchViewModelTest.kt b/feature/matcher/src/test/java/com/simprints/matcher/screen/MatchViewModelTest.kt index 8629422436..8f3d164bd8 100644 --- a/feature/matcher/src/test/java/com/simprints/matcher/screen/MatchViewModelTest.kt +++ b/feature/matcher/src/test/java/com/simprints/matcher/screen/MatchViewModelTest.kt @@ -109,6 +109,7 @@ internal class MatchViewModelTest { viewModel.matchState.test() viewModel.setupMatch( MatchParams( + probeReferenceId = "referenceId", probeFaceSamples = listOf(getFaceSample()), flowType = FlowType.ENROL, queryForCandidates = mockk {}, @@ -153,6 +154,7 @@ internal class MatchViewModelTest { val states = viewModel.matchState.test() viewModel.setupMatch( MatchParams( + probeReferenceId = "referenceId", probeFaceSamples = listOf(getFaceSample()), flowType = FlowType.ENROL, queryForCandidates = mockk {}, @@ -207,6 +209,7 @@ internal class MatchViewModelTest { viewModel.setupMatch( MatchParams( + probeReferenceId = "referenceId", probeFingerprintSamples = listOf(getFingerprintSample()), fingerprintSDK = SECUGEN_SIM_MATCHER, flowType = FlowType.ENROL, diff --git a/feature/matcher/src/test/java/com/simprints/matcher/usecases/FaceMatcherUseCaseTest.kt b/feature/matcher/src/test/java/com/simprints/matcher/usecases/FaceMatcherUseCaseTest.kt index 1ab631ad2b..11fe0458b7 100644 --- a/feature/matcher/src/test/java/com/simprints/matcher/usecases/FaceMatcherUseCaseTest.kt +++ b/feature/matcher/src/test/java/com/simprints/matcher/usecases/FaceMatcherUseCaseTest.kt @@ -65,6 +65,7 @@ internal class FaceMatcherUseCaseTest { val results = useCase.invoke( MatchParams( + probeReferenceId = "referenceId", flowType = FlowType.VERIFY, queryForCandidates = SubjectQuery(), biometricDataSource = BiometricDataSource.Simprints, @@ -89,6 +90,7 @@ internal class FaceMatcherUseCaseTest { val results = useCase.invoke( MatchParams( + probeReferenceId = "referenceId", probeFaceSamples = listOf( MatchParams.FaceSample("faceId", byteArrayOf(1, 2, 3)), ), @@ -127,6 +129,7 @@ internal class FaceMatcherUseCaseTest { val results = useCase.invoke( matchParams = MatchParams( + probeReferenceId = "referenceId", probeFaceSamples = listOf( MatchParams.FaceSample("faceId", byteArrayOf(1, 2, 3)), ), diff --git a/feature/matcher/src/test/java/com/simprints/matcher/usecases/FingerprintMatcherUseCaseTest.kt b/feature/matcher/src/test/java/com/simprints/matcher/usecases/FingerprintMatcherUseCaseTest.kt index 02547326f1..e2ac1349de 100644 --- a/feature/matcher/src/test/java/com/simprints/matcher/usecases/FingerprintMatcherUseCaseTest.kt +++ b/feature/matcher/src/test/java/com/simprints/matcher/usecases/FingerprintMatcherUseCaseTest.kt @@ -74,6 +74,7 @@ internal class FingerprintMatcherUseCaseTest { fun `Skips matching if there are no probes`() = runTest { val results = useCase.invoke( MatchParams( + probeReferenceId = "referenceId", probeFingerprintSamples = emptyList(), fingerprintSDK = SECUGEN_SIM_MATCHER, flowType = FlowType.VERIFY, @@ -102,6 +103,7 @@ internal class FingerprintMatcherUseCaseTest { val results = useCase.invoke( MatchParams( + probeReferenceId = "referenceId", probeFingerprintSamples = listOf( MatchParams.FingerprintSample( IFingerIdentifier.LEFT_3RD_FINGER, @@ -161,6 +163,7 @@ internal class FingerprintMatcherUseCaseTest { useCase.invoke( matchParams = MatchParams( + probeReferenceId = "referenceId", probeFingerprintSamples = listOf( MatchParams.FingerprintSample( IFingerIdentifier.LEFT_3RD_FINGER, diff --git a/feature/matcher/src/test/java/com/simprints/matcher/usecases/SaveMatchEventUseCaseTest.kt b/feature/matcher/src/test/java/com/simprints/matcher/usecases/SaveMatchEventUseCaseTest.kt index 3169497cb7..d06621240d 100644 --- a/feature/matcher/src/test/java/com/simprints/matcher/usecases/SaveMatchEventUseCaseTest.kt +++ b/feature/matcher/src/test/java/com/simprints/matcher/usecases/SaveMatchEventUseCaseTest.kt @@ -67,6 +67,7 @@ class SaveMatchEventUseCaseTest { Timestamp(1L), Timestamp(2L), MatchParams( + probeReferenceId = "referenceId", flowType = FlowType.VERIFY, queryForCandidates = SubjectQuery(subjectId = "subjectId"), probeFaceSamples = listOf(MatchParams.FaceSample("faceId", byteArrayOf(1, 2, 3))), @@ -91,6 +92,7 @@ class SaveMatchEventUseCaseTest { assertThat(it.payload.matcher).isEqualTo("faceMatcherName") assertThat(it.payload.result?.candidateId).isEqualTo("guid1") assertThat(it.payload.result?.score).isEqualTo(0.5f) + // TODO verify reference id }, ) } @@ -102,6 +104,7 @@ class SaveMatchEventUseCaseTest { Timestamp(1L), Timestamp(2L), MatchParams( + probeReferenceId = "referenceId", flowType = FlowType.VERIFY, queryForCandidates = SubjectQuery(subjectId = "subjectId"), probeFingerprintSamples = listOf( @@ -133,6 +136,7 @@ class SaveMatchEventUseCaseTest { assertThat(it.payload.matcher).isEqualTo("faceMatcherName") assertThat(it.payload.result?.candidateId).isEqualTo("guid1") assertThat(it.payload.result?.score).isEqualTo(0.5f) + // TODO verify reference id }, ) } @@ -144,6 +148,7 @@ class SaveMatchEventUseCaseTest { startTime = Timestamp(1L), endTime = Timestamp(2L), matchParams = MatchParams( + probeReferenceId = "referenceId", probeFaceSamples = emptyList(), probeFingerprintSamples = emptyList(), fingerprintSDK = null, @@ -177,6 +182,7 @@ class SaveMatchEventUseCaseTest { ?.last() ?.candidateId, ).isEqualTo("guid2") + // TODO verify reference id }, ) } @@ -188,6 +194,7 @@ class SaveMatchEventUseCaseTest { Timestamp(1L), Timestamp(2L), MatchParams( + probeReferenceId = "referenceId", flowType = FlowType.IDENTIFY, queryForCandidates = SubjectQuery(attendantId = "userId".asTokenizableEncrypted()), biometricDataSource = BiometricDataSource.Simprints, @@ -213,6 +220,7 @@ class SaveMatchEventUseCaseTest { Timestamp(1L), Timestamp(2L), MatchParams( + probeReferenceId = "referenceId", flowType = FlowType.IDENTIFY, queryForCandidates = SubjectQuery(moduleId = "moduleId".asTokenizableEncrypted()), biometricDataSource = BiometricDataSource.Simprints, @@ -238,6 +246,7 @@ class SaveMatchEventUseCaseTest { Timestamp(1L), Timestamp(2L), MatchParams( + probeReferenceId = "referenceId", emptyList(), flowType = FlowType.IDENTIFY, queryForCandidates = SubjectQuery(), diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt index 64f08e379b..3688d0b6a8 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt @@ -226,7 +226,7 @@ internal class OrchestratorViewModel @Inject constructor( .map { MatchParams.FaceSample(it.faceId, it.template) } val newPayload = matchingStep.payload .getParcelable(MatchStepStubPayload.STUB_KEY) - ?.toFaceStepArgs(faceSamples) + ?.toFaceStepArgs(result.referenceId, faceSamples) if (newPayload != null) { matchingStep.payload = newPayload @@ -257,7 +257,7 @@ internal class OrchestratorViewModel @Inject constructor( } val newPayload = matchingStep.payload .getParcelable(MatchStepStubPayload.STUB_KEY) - ?.toFingerprintStepArgs(fingerprintSamples) + ?.toFingerprintStepArgs(result.referenceId, fingerprintSamples) if (newPayload != null) { matchingStep.payload = newPayload diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/steps/MatchStepStubPayload.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/steps/MatchStepStubPayload.kt index 2d9d2601cd..188518d65b 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/steps/MatchStepStubPayload.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/steps/MatchStepStubPayload.kt @@ -23,14 +23,22 @@ internal data class MatchStepStubPayload( val biometricDataSource: BiometricDataSource, val fingerprintSDK: FingerprintConfiguration.BioSdk?, ) : Parcelable { - fun toFaceStepArgs(samples: List) = MatchContract.getArgs( + fun toFaceStepArgs( + referenceId: String, + samples: List, + ) = MatchContract.getArgs( + referenceId = referenceId, faceSamples = samples, flowType = flowType, subjectQuery = subjectQuery, biometricDataSource = biometricDataSource, ) - fun toFingerprintStepArgs(samples: List) = MatchContract.getArgs( + fun toFingerprintStepArgs( + referenceId: String, + samples: List, + ) = MatchContract.getArgs( + referenceId = referenceId, fingerprintSamples = samples, fingerprintSDK = fingerprintSDK, flowType = flowType, diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/OrchestratorViewModelTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/OrchestratorViewModelTest.kt index 1555e1c9bd..2095efc377 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/OrchestratorViewModelTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/OrchestratorViewModelTest.kt @@ -256,7 +256,7 @@ internal class OrchestratorViewModelTest { coEvery { shouldCreatePerson(any(), any(), any()) } returns false viewModel.handleAction(mockk()) - viewModel.handleResult(FaceCaptureResult(emptyList())) + viewModel.handleResult(FaceCaptureResult("", emptyList())) viewModel.currentStep.test().value().peekContent()?.let { step -> assertThat(step.id).isEqualTo(StepId.FACE_MATCHER) @@ -280,7 +280,7 @@ internal class OrchestratorViewModelTest { coEvery { shouldCreatePerson(any(), any(), any()) } returns false viewModel.handleAction(mockk()) - viewModel.handleResult(FingerprintCaptureResult(emptyList())) + viewModel.handleResult(FingerprintCaptureResult("", emptyList())) viewModel.currentStep.test().value().peekContent()?.let { step -> assertThat(step.id).isEqualTo(StepId.FINGERPRINT_MATCHER) @@ -348,7 +348,7 @@ internal class OrchestratorViewModelTest { ) viewModel.handleAction(mockk()) - viewModel.handleResult(FingerprintCaptureResult(captureResults)) + viewModel.handleResult(FingerprintCaptureResult("", captureResults)) viewModel.currentStep.test().value().peekContent()?.let { step -> assertThat(step.id).isEqualTo(StepId.FINGERPRINT_MATCHER) @@ -456,7 +456,7 @@ internal class OrchestratorViewModelTest { ) viewModel.handleAction(mockk()) - viewModel.handleResult(FingerprintCaptureResult(emptyList())) + viewModel.handleResult(FingerprintCaptureResult("", emptyList())) viewModel.currentStep.test().value().peekContent()?.let { step -> assertThat(step.payload.getParcelable("params")?.steps) 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 d963d7be9f..57e93b0da1 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 @@ -60,6 +60,7 @@ class OrchestratorCacheIntegrationTest { payload = bundleOf("key" to "value"), status = StepStatus.COMPLETED, result = FingerprintCaptureResult( + "", results = listOf( FingerprintCaptureResult.Item( captureEventId = GUID1, diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCaseTest.kt index 04ae5336c2..dfa01c6e50 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCaseTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCaseTest.kt @@ -59,7 +59,7 @@ internal class CreatePersonEventUseCaseTest { fun `Create event if there is face biometric data`() = runTest { coEvery { eventRepository.getEventsInCurrentSession() } returns listOf() - useCase(listOf(FaceCaptureResult(listOf(createFaceCaptureResultItem())))) + useCase(listOf(FaceCaptureResult("", listOf(createFaceCaptureResultItem())))) coVerify { eventRepository.addOrUpdateEvent( @@ -79,7 +79,7 @@ internal class CreatePersonEventUseCaseTest { }, ) - useCase(listOf(FingerprintCaptureResult(listOf(createFingerprintCaptureResultItem())))) + useCase(listOf(FingerprintCaptureResult("", listOf(createFingerprintCaptureResultItem())))) coVerify { eventRepository.addOrUpdateEvent( @@ -99,7 +99,7 @@ internal class CreatePersonEventUseCaseTest { }, ) - useCase(listOf(FaceCaptureResult(listOf(createFaceCaptureResultItem())))) + useCase(listOf(FaceCaptureResult("", listOf(createFaceCaptureResultItem())))) coVerify { eventRepository.addOrUpdateEvent( @@ -121,7 +121,7 @@ internal class CreatePersonEventUseCaseTest { }, ) - useCase(listOf(FingerprintCaptureResult(listOf(createFingerprintCaptureResultItem())))) + useCase(listOf(FingerprintCaptureResult("", listOf(createFingerprintCaptureResultItem())))) coVerify { eventRepository.addOrUpdateEvent( @@ -149,7 +149,7 @@ internal class CreatePersonEventUseCaseTest { }, ) - useCase(listOf(FingerprintCaptureResult(listOf(createFingerprintCaptureResultItem())))) + useCase(listOf(FingerprintCaptureResult("", listOf(createFingerprintCaptureResultItem())))) coVerify { eventRepository.addOrUpdateEvent( diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/MapRefusalOrErrorResultUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/MapRefusalOrErrorResultUseCaseTest.kt index 5600e54ba5..310279d2d3 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/MapRefusalOrErrorResultUseCaseTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/MapRefusalOrErrorResultUseCaseTest.kt @@ -63,7 +63,7 @@ class MapRefusalOrErrorResultUseCaseTest { listOf( FetchSubjectResult(found = true), SetupResult(isSuccess = true), - FaceCaptureResult(emptyList()), + FaceCaptureResult("", emptyList()), ).forEach { result -> assertThat(useCase(result, mockk())).isNull() } } diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/MapStepsForLastBiometricEnrolUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/MapStepsForLastBiometricEnrolUseCaseTest.kt index c597a9450d..e3566b7f73 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/MapStepsForLastBiometricEnrolUseCaseTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/MapStepsForLastBiometricEnrolUseCaseTest.kt @@ -51,6 +51,7 @@ internal class MapStepsForLastBiometricEnrolUseCaseTest { val result = useCase( listOf( FaceCaptureResult( + "", results = listOf( FaceCaptureResult.Item(captureEventId = null, index = 0, sample = null), FaceCaptureResult.Item( @@ -98,6 +99,7 @@ internal class MapStepsForLastBiometricEnrolUseCaseTest { val result = useCase( listOf( FingerprintCaptureResult( + "", results = listOf( FingerprintCaptureResult.Item(null, IFingerIdentifier.LEFT_THUMB, null), FingerprintCaptureResult.Item( diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/ShouldCreatePersonUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/ShouldCreatePersonUseCaseTest.kt index 353a0deba4..315d165a7b 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/ShouldCreatePersonUseCaseTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/ShouldCreatePersonUseCaseTest.kt @@ -42,7 +42,7 @@ class ShouldCreatePersonUseCaseTest { actionRequest = null, modalities = setOf(GeneralConfiguration.Modality.FINGERPRINT), results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult(emptyList())), + createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), ), ), ).isFalse() @@ -63,7 +63,7 @@ class ShouldCreatePersonUseCaseTest { ), modalities = setOf(GeneralConfiguration.Modality.FINGERPRINT), results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult(emptyList())), + createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), ), ), ).isTrue() @@ -84,7 +84,7 @@ class ShouldCreatePersonUseCaseTest { ), modalities = setOf(GeneralConfiguration.Modality.FINGERPRINT), results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult(emptyList())), + createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), ), ), ).isTrue() @@ -106,7 +106,7 @@ class ShouldCreatePersonUseCaseTest { ), modalities = setOf(GeneralConfiguration.Modality.FINGERPRINT), results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult(emptyList())), + createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), ), ), ).isTrue() @@ -127,7 +127,7 @@ class ShouldCreatePersonUseCaseTest { ), modalities = setOf(GeneralConfiguration.Modality.FINGERPRINT), results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult(emptyList())), + createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), ), ), ).isFalse() @@ -148,7 +148,7 @@ class ShouldCreatePersonUseCaseTest { ), modalities = setOf(GeneralConfiguration.Modality.FINGERPRINT), results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult(emptyList())), + createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), ), ), ).isTrue() @@ -161,7 +161,7 @@ class ShouldCreatePersonUseCaseTest { actionRequest = flowAction, modalities = emptySet(), results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult(emptyList())), + createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), ), ), ).isFalse() @@ -210,7 +210,7 @@ class ShouldCreatePersonUseCaseTest { actionRequest = flowAction, modalities = setOf(GeneralConfiguration.Modality.FINGERPRINT), results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult(emptyList())), + createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), ), ), ).isTrue() @@ -227,7 +227,7 @@ class ShouldCreatePersonUseCaseTest { ), results = listOf( createStep(StepId.FACE_CAPTURE, null), - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult(emptyList())), + createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), ), ), ).isFalse() @@ -243,7 +243,7 @@ class ShouldCreatePersonUseCaseTest { GeneralConfiguration.Modality.FINGERPRINT, ), results = listOf( - createStep(StepId.FACE_CAPTURE, FaceCaptureResult(emptyList())), + createStep(StepId.FACE_CAPTURE, FaceCaptureResult("", emptyList())), createStep(StepId.FINGERPRINT_CAPTURE, null), ), ), @@ -256,7 +256,7 @@ class ShouldCreatePersonUseCaseTest { useCase( actionRequest = flowAction, modalities = setOf(GeneralConfiguration.Modality.FACE), - results = listOf(createStep(StepId.FACE_CAPTURE, FaceCaptureResult(emptyList()))), + results = listOf(createStep(StepId.FACE_CAPTURE, FaceCaptureResult("", emptyList()))), ), ).isTrue() } @@ -271,8 +271,8 @@ class ShouldCreatePersonUseCaseTest { GeneralConfiguration.Modality.FINGERPRINT, ), results = listOf( - createStep(StepId.FACE_CAPTURE, FaceCaptureResult(emptyList())), - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult(emptyList())), + createStep(StepId.FACE_CAPTURE, FaceCaptureResult("", emptyList())), + createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), ), ), ).isTrue() @@ -280,7 +280,7 @@ class ShouldCreatePersonUseCaseTest { @Test fun `Returns true when there is a PersonCreationEvent but it's missing a face reference that was scheduled for capture`() = runTest { - val faceCaptureResults = listOf(createStep(StepId.FACE_CAPTURE, FaceCaptureResult(emptyList()))) + val faceCaptureResults = listOf(createStep(StepId.FACE_CAPTURE, FaceCaptureResult("", emptyList()))) val sessionEvents = listOf( PersonCreationEvent( @@ -308,7 +308,7 @@ class ShouldCreatePersonUseCaseTest { @Test fun `Returns true when there is a PersonCreationEvent but it's missing a fingerprint reference that was scheduled for capture`() = runTest { - val fingerprintCaptureResults = listOf(createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult(emptyList()))) + val fingerprintCaptureResults = listOf(createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList()))) val sessionEvents = listOf( PersonCreationEvent( @@ -361,8 +361,8 @@ class ShouldCreatePersonUseCaseTest { GeneralConfiguration.Modality.FINGERPRINT, ), results = listOf( - createStep(StepId.FACE_CAPTURE, FaceCaptureResult(emptyList())), - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult(emptyList())), + createStep(StepId.FACE_CAPTURE, FaceCaptureResult("", emptyList())), + createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), ), ), ).isFalse() diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/CreateEnrolResponseUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/CreateEnrolResponseUseCaseTest.kt index e3631b6d2b..476ef8afec 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/CreateEnrolResponseUseCaseTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/CreateEnrolResponseUseCaseTest.kt @@ -56,8 +56,8 @@ internal class CreateEnrolResponseUseCaseTest { useCase( action, listOf( - FingerprintCaptureResult(emptyList()), - FaceCaptureResult(emptyList()), + FingerprintCaptureResult("", emptyList()), + FaceCaptureResult("", emptyList()), mockk(), ), project 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 c5c2332c57..5fae8aba48 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 @@ -7,6 +7,7 @@ import java.io.Serializable @Keep data class FingerprintCaptureResult( + val referenceId: String, var results: List, ) : Serializable { @Keep diff --git a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt index 61b52cb051..fdb00bba65 100644 --- a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt +++ b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt @@ -679,7 +679,7 @@ internal class FingerprintCaptureViewModel @Inject constructor( .orEmpty() addBiometricReferenceCreatedEvents(biometricReferenceId, resultItems.mapNotNull { it.captureEventId }) - _finishWithFingerprints.send(FingerprintCaptureResult(resultItems)) + _finishWithFingerprints.send(FingerprintCaptureResult(biometricReferenceId, resultItems)) } private suspend fun saveImageIfExists( diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactoryTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactoryTest.kt index 0b09d66248..d96e76033d 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactoryTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactoryTest.kt @@ -150,6 +150,7 @@ class SubjectFactoryTest { attendantId = expected.attendantId, moduleId = expected.moduleId, fingerprintResponse = FingerprintCaptureResult( + GUID1, listOf( FingerprintCaptureResult.Item( captureEventId = GUID1, @@ -165,6 +166,7 @@ class SubjectFactoryTest { ), ), faceResponse = FaceCaptureResult( + GUID1, listOf( FaceCaptureResult.Item( captureEventId = GUID1, From 0429c7c0ef7559dcf72b8495cc38692412bcb047 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 10 Feb 2025 11:02:42 +0200 Subject: [PATCH 07/22] MS-871 Add probe reference ID field to match events --- .../matcher/usecases/SaveMatchEventUseCase.kt | 28 ++++---- .../usecases/SaveMatchEventUseCaseTest.kt | 8 ++- .../remote/models/ApiOneToManyMatchPayload.kt | 15 ++-- .../remote/models/ApiOneToOneMatchPayload.kt | 17 +++-- .../eventsync/event/EventValidationUtils.kt | 10 +-- .../events/sampledata/EventFactoryUtils.kt | 2 + .../domain/models/OneToManyMatchEvent.kt | 65 ++++++++++++++--- .../event/domain/models/OneToOneMatchEvent.kt | 62 ++++++++++++++--- .../event/domain/models/EventPayloadTest.kt | 2 + .../domain/models/OneToManyMatchEventTest.kt | 69 ++++++++++++++++++- .../domain/models/OneToOneMatchEventTest.kt | 64 +++++++++++++++++ 11 files changed, 293 insertions(+), 49 deletions(-) diff --git a/feature/matcher/src/main/java/com/simprints/matcher/usecases/SaveMatchEventUseCase.kt b/feature/matcher/src/main/java/com/simprints/matcher/usecases/SaveMatchEventUseCase.kt index cdd54f0154..8efa3d8848 100644 --- a/feature/matcher/src/main/java/com/simprints/matcher/usecases/SaveMatchEventUseCase.kt +++ b/feature/matcher/src/main/java/com/simprints/matcher/usecases/SaveMatchEventUseCase.kt @@ -77,14 +77,15 @@ internal class SaveMatchEventUseCase @Inject constructor( queryForCandidates: SubjectQuery, matchEntry: MatchEntry?, fingerComparisonStrategy: FingerComparisonStrategy?, - biometricReferenceId: String, // TODO add field to event + biometricReferenceId: String, ) = OneToOneMatchEvent( - startTime, - endTime, - queryForCandidates.subjectId!!, - matcherName, - matchEntry, - fingerComparisonStrategy, + createdAt = startTime, + endTime = endTime, + candidateId = queryForCandidates.subjectId!!, + matcher = matcherName, + result = matchEntry, + fingerComparisonStrategy = fingerComparisonStrategy, + probeBiometricReferenceId = biometricReferenceId, ) private fun getOneToManyEvent( @@ -94,16 +95,17 @@ internal class SaveMatchEventUseCase @Inject constructor( queryForCandidates: SubjectQuery, candidatesCount: Int, matchEntries: List, - biometricReferenceId: String, // TODO add field to event + biometricReferenceId: String, ) = OneToManyMatchEvent( - startTime, - endTime, - OneToManyMatchEvent.OneToManyMatchPayload.MatchPool( + createdAt = startTime, + endTime = endTime, + pool = OneToManyMatchEvent.OneToManyMatchPayload.MatchPool( queryForCandidates.parseQueryAsCoreMatchPoolType(), candidatesCount, ), - matcherName, - matchEntries, + matcher = matcherName, + result = matchEntries, + probeBiometricReferenceId = biometricReferenceId, ) private fun SubjectQuery.parseQueryAsCoreMatchPoolType(): OneToManyMatchEvent.OneToManyMatchPayload.MatchPoolType = when { diff --git a/feature/matcher/src/test/java/com/simprints/matcher/usecases/SaveMatchEventUseCaseTest.kt b/feature/matcher/src/test/java/com/simprints/matcher/usecases/SaveMatchEventUseCaseTest.kt index d06621240d..ff852c70f1 100644 --- a/feature/matcher/src/test/java/com/simprints/matcher/usecases/SaveMatchEventUseCaseTest.kt +++ b/feature/matcher/src/test/java/com/simprints/matcher/usecases/SaveMatchEventUseCaseTest.kt @@ -11,7 +11,9 @@ import com.simprints.infra.config.sync.ConfigManager 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.event.domain.models.OneToManyMatchEvent +import com.simprints.infra.events.event.domain.models.OneToManyMatchEvent.OneToManyMatchPayload.OneToManyMatchPayloadV3 import com.simprints.infra.events.event.domain.models.OneToOneMatchEvent +import com.simprints.infra.events.event.domain.models.OneToOneMatchEvent.OneToOneMatchPayload.OneToOneMatchPayloadV4 import com.simprints.infra.events.session.SessionEventRepository import com.simprints.matcher.FaceMatchResult import com.simprints.matcher.MatchParams @@ -92,7 +94,7 @@ class SaveMatchEventUseCaseTest { assertThat(it.payload.matcher).isEqualTo("faceMatcherName") assertThat(it.payload.result?.candidateId).isEqualTo("guid1") assertThat(it.payload.result?.score).isEqualTo(0.5f) - // TODO verify reference id + assertThat((it.payload as OneToOneMatchPayloadV4).probeBiometricReferenceId).isEqualTo("referenceId") }, ) } @@ -136,7 +138,7 @@ class SaveMatchEventUseCaseTest { assertThat(it.payload.matcher).isEqualTo("faceMatcherName") assertThat(it.payload.result?.candidateId).isEqualTo("guid1") assertThat(it.payload.result?.score).isEqualTo(0.5f) - // TODO verify reference id + assertThat((it.payload as OneToOneMatchPayloadV4).probeBiometricReferenceId).isEqualTo("referenceId") }, ) } @@ -182,7 +184,7 @@ class SaveMatchEventUseCaseTest { ?.last() ?.candidateId, ).isEqualTo("guid2") - // TODO verify reference id + assertThat((it.payload as OneToManyMatchPayloadV3).probeBiometricReferenceId).isEqualTo("referenceId") }, ) } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToManyMatchPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToManyMatchPayload.kt index c14f85db49..56f7f29d27 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToManyMatchPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToManyMatchPayload.kt @@ -12,6 +12,7 @@ internal data class ApiOneToManyMatchPayload( val pool: ApiMatchPool, val matcher: String, val result: List?, + val probeBiometricReferenceId: String? = null, ) : ApiEventPayload(startTime) { @Keep data class ApiMatchPool( @@ -30,11 +31,15 @@ internal data class ApiOneToManyMatchPayload( } constructor(domainPayload: OneToManyMatchPayload) : this( - domainPayload.createdAt.fromDomainToApi(), - domainPayload.endedAt?.fromDomainToApi(), - ApiMatchPool(domainPayload.pool), - domainPayload.matcher, - domainPayload.result?.map { ApiMatchEntry(it) }, + startTime = domainPayload.createdAt.fromDomainToApi(), + endTime = domainPayload.endedAt?.fromDomainToApi(), + pool = ApiMatchPool(domainPayload.pool), + matcher = domainPayload.matcher, + result = domainPayload.result?.map { ApiMatchEntry(it) }, + probeBiometricReferenceId = when (domainPayload) { + is OneToManyMatchPayload.OneToManyMatchPayloadV2 -> null + is OneToManyMatchPayload.OneToManyMatchPayloadV3 -> domainPayload.probeBiometricReferenceId + }, ) override fun getTokenizedFieldJsonPath(tokenKeyType: TokenKeyType): String? = null // this payload doesn't have tokenizable fields diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToOneMatchPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToOneMatchPayload.kt index a0bdcab049..71107450a1 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToOneMatchPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiOneToOneMatchPayload.kt @@ -15,14 +15,19 @@ internal data class ApiOneToOneMatchPayload( val matcher: String, val result: ApiMatchEntry?, val fingerComparisonStrategy: ApiFingerComparisonStrategy?, + val probeBiometricReferenceId: String? = null, ) : ApiEventPayload(startTime) { constructor(domainPayload: OneToOneMatchPayload) : this( - domainPayload.createdAt.fromDomainToApi(), - domainPayload.endedAt?.fromDomainToApi(), - domainPayload.candidateId, - domainPayload.matcher, - domainPayload.result?.let { ApiMatchEntry(it) }, - domainPayload.fingerComparisonStrategy?.fromDomainToApi(), + startTime = domainPayload.createdAt.fromDomainToApi(), + endTime = domainPayload.endedAt?.fromDomainToApi(), + candidateId = domainPayload.candidateId, + matcher = domainPayload.matcher, + result = domainPayload.result?.let { ApiMatchEntry(it) }, + fingerComparisonStrategy = domainPayload.fingerComparisonStrategy?.fromDomainToApi(), + when (domainPayload) { + is OneToOneMatchPayload.OneToOneMatchPayloadV3 -> null + is OneToOneMatchPayload.OneToOneMatchPayloadV4 -> domainPayload.probeBiometricReferenceId + }, ) override fun getTokenizedFieldJsonPath(tokenKeyType: TokenKeyType): String? = null // this payload doesn't have tokenizable fields diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt index 6125a39b4c..06976423a6 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/EventValidationUtils.kt @@ -382,7 +382,7 @@ fun validateMatchEntryApiModel(json: JSONObject) { } fun validateOneToManyMatchEventApiModel(json: JSONObject) { - validateCommonParams(json, "OneToManyMatch", 2) + validateCommonParams(json, "OneToManyMatch", 3) with(json.getJSONObject("payload")) { validateTimestamp(getJSONObject("startTime")) @@ -397,12 +397,13 @@ fun validateOneToManyMatchEventApiModel(json: JSONObject) { for (i in 0 until matchEntries.length()) { validateMatchEntryApiModel(matchEntries.getJSONObject(i)) } - assertThat(length()).isEqualTo(5) + assertThat(getString("probeBiometricReferenceId").isValidGuid()).isTrue() + assertThat(length()).isEqualTo(6) } } fun validateOneToOneMatchEventApiModel(json: JSONObject) { - validateCommonParams(json, "OneToOneMatch", 3) + validateCommonParams(json, "OneToOneMatch", 4) with(json.getJSONObject("payload")) { validateTimestamp(getJSONObject("startTime")) @@ -417,7 +418,8 @@ fun validateOneToOneMatchEventApiModel(json: JSONObject) { with(getJSONObject("result")) { validateMatchEntryApiModel(this) } - assertThat(length()).isEqualTo(6) + assertThat(getString("probeBiometricReferenceId").isValidGuid()).isTrue() + assertThat(length()).isEqualTo(7) } } diff --git a/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt b/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt index 3397129a0e..754919954a 100644 --- a/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt +++ b/infra/events/src/debug/java/com/simprints/infra/events/sampledata/EventFactoryUtils.kt @@ -367,6 +367,7 @@ fun createOneToManyMatchEvent() = OneToManyMatchEvent( MatchPool(PROJECT, 100), "RANK_ONE", listOf(MatchEntry(GUID1, 0F)), + GUID2, ) fun createOneToOneMatchEvent() = OneToOneMatchEvent( @@ -376,6 +377,7 @@ fun createOneToOneMatchEvent() = OneToOneMatchEvent( "SIM_AFIS", MatchEntry(GUID1, 10F), FingerComparisonStrategy.CROSS_FINGER_USING_MEAN_OF_MAX, + GUID2, ) fun createPersonCreationEvent() = PersonCreationEvent( diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/OneToManyMatchEvent.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/OneToManyMatchEvent.kt index cd712ceb38..ff7f1a0a06 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/OneToManyMatchEvent.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/OneToManyMatchEvent.kt @@ -1,6 +1,8 @@ package com.simprints.infra.events.event.domain.models import androidx.annotation.Keep +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo import com.simprints.core.domain.tokenization.TokenizableString import com.simprints.core.tools.time.Timestamp import com.simprints.infra.config.store.models.TokenKeyType @@ -21,28 +23,74 @@ data class OneToManyMatchEvent( pool: OneToManyMatchPayload.MatchPool, matcher: String, result: List?, + probeBiometricReferenceId: String, ) : this( - UUID.randomUUID().toString(), - OneToManyMatchPayload(createdAt, EVENT_VERSION, endTime, pool, matcher, result), - ONE_TO_MANY_MATCH, + id = UUID.randomUUID().toString(), + payload = OneToManyMatchPayload.OneToManyMatchPayloadV3( + createdAt = createdAt, + eventVersion = EVENT_VERSION, + endedAt = endTime, + pool = pool, + matcher = matcher, + result = result, + probeBiometricReferenceId = probeBiometricReferenceId, + ), + type = ONE_TO_MANY_MATCH, ) override fun getTokenizableFields(): Map = emptyMap() override fun setTokenizedFields(map: Map) = this // No tokenized fields + @JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "eventVersion", + visible = true, + ) + @JsonSubTypes( + JsonSubTypes.Type( + value = OneToManyMatchPayload.OneToManyMatchPayloadV2::class, + name = EVENT_VERSION_WITHOUT_REFERENCE_ID.toString(), + ), + JsonSubTypes.Type( + value = OneToManyMatchPayload.OneToManyMatchPayloadV3::class, + name = EVENT_VERSION.toString(), + ), + ) @Keep - data class OneToManyMatchPayload( + sealed class OneToManyMatchPayload( override val createdAt: Timestamp, override val eventVersion: Int, override val endedAt: Timestamp?, - val pool: MatchPool, - val matcher: String, - val result: List?, + open val pool: MatchPool, + open val matcher: String, + open val result: List?, override val type: EventType = ONE_TO_MANY_MATCH, ) : EventPayload() { override fun toSafeString(): String = "matcher: $matcher, pool: ${pool.type}, size: ${pool.count}, results: ${result?.size}" + @Keep + data class OneToManyMatchPayloadV2( + override val createdAt: Timestamp, + override val eventVersion: Int, + override val endedAt: Timestamp?, + override val pool: MatchPool, + override val matcher: String, + override val result: List?, + ) : OneToManyMatchPayload(createdAt, eventVersion, endedAt, pool, matcher, result) + + @Keep + data class OneToManyMatchPayloadV3( + override val createdAt: Timestamp, + override val eventVersion: Int, + override val endedAt: Timestamp?, + override val pool: MatchPool, + override val matcher: String, + override val result: List?, + val probeBiometricReferenceId: String, + ) : OneToManyMatchPayload(createdAt, eventVersion, endedAt, pool, matcher, result) + @Keep data class MatchPool( val type: MatchPoolType, @@ -58,6 +106,7 @@ data class OneToManyMatchEvent( } companion object { - const val EVENT_VERSION = 2 + const val EVENT_VERSION_WITHOUT_REFERENCE_ID = 2 + const val EVENT_VERSION = 3 } } diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/OneToOneMatchEvent.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/OneToOneMatchEvent.kt index 155e80ee85..db70b20275 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/OneToOneMatchEvent.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/OneToOneMatchEvent.kt @@ -1,6 +1,8 @@ package com.simprints.infra.events.event.domain.models import androidx.annotation.Keep +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo import com.simprints.core.domain.tokenization.TokenizableString import com.simprints.core.tools.time.Timestamp import com.simprints.infra.config.store.models.TokenKeyType @@ -22,9 +24,10 @@ data class OneToOneMatchEvent( matcher: String, result: MatchEntry?, fingerComparisonStrategy: FingerComparisonStrategy?, + probeBiometricReferenceId: String, ) : this( - UUID.randomUUID().toString(), - OneToOneMatchPayload( + id = UUID.randomUUID().toString(), + payload = OneToOneMatchPayload.OneToOneMatchPayloadV4( createdAt = createdAt, eventVersion = EVENT_VERSION, endedAt = endTime, @@ -32,30 +35,71 @@ data class OneToOneMatchEvent( matcher = matcher, result = result, fingerComparisonStrategy = fingerComparisonStrategy, + probeBiometricReferenceId = probeBiometricReferenceId, ), - ONE_TO_ONE_MATCH, + type = ONE_TO_ONE_MATCH, ) override fun getTokenizableFields(): Map = emptyMap() override fun setTokenizedFields(map: Map) = this // No tokenized fields + @JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "eventVersion", + visible = true, + ) + @JsonSubTypes( + JsonSubTypes.Type( + value = OneToOneMatchPayload.OneToOneMatchPayloadV3::class, + name = VERSION_WITHOUT_REFERENCE_ID.toString(), + ), + JsonSubTypes.Type( + value = OneToOneMatchPayload.OneToOneMatchPayloadV4::class, + name = EVENT_VERSION.toString(), + ), + ) @Keep - data class OneToOneMatchPayload( + sealed class OneToOneMatchPayload( override val createdAt: Timestamp, override val eventVersion: Int, override var endedAt: Timestamp?, - val candidateId: String, - val matcher: String, - val result: MatchEntry?, - val fingerComparisonStrategy: FingerComparisonStrategy?, + open val candidateId: String, + open val matcher: String, + open val result: MatchEntry?, + open val fingerComparisonStrategy: FingerComparisonStrategy?, override val type: EventType = ONE_TO_ONE_MATCH, ) : EventPayload() { override fun toSafeString(): String = "matcher: $matcher, candidate ID: $candidateId, " + "result: ${result?.score}, finger strategy: $fingerComparisonStrategy" + + @Keep + data class OneToOneMatchPayloadV3( + override val createdAt: Timestamp, + override val eventVersion: Int, + override var endedAt: Timestamp?, + override val candidateId: String, + override val matcher: String, + override val result: MatchEntry?, + override val fingerComparisonStrategy: FingerComparisonStrategy?, + ) : OneToOneMatchPayload(createdAt, eventVersion, endedAt, candidateId, matcher, result, fingerComparisonStrategy) + + @Keep + data class OneToOneMatchPayloadV4( + override val createdAt: Timestamp, + override val eventVersion: Int, + override var endedAt: Timestamp?, + override val candidateId: String, + override val matcher: String, + override val result: MatchEntry?, + override val fingerComparisonStrategy: FingerComparisonStrategy?, + val probeBiometricReferenceId: String, + ) : OneToOneMatchPayload(createdAt, eventVersion, endedAt, candidateId, matcher, result, fingerComparisonStrategy) } companion object { - const val EVENT_VERSION = 3 + const val VERSION_WITHOUT_REFERENCE_ID = 3 + const val EVENT_VERSION = 4 } } diff --git a/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EventPayloadTest.kt b/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EventPayloadTest.kt index 8242590e7d..118b4e440f 100644 --- a/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EventPayloadTest.kt +++ b/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/EventPayloadTest.kt @@ -217,6 +217,7 @@ class EventPayloadTest { pool = MatchPool(PROJECT, 100), matcher = "MATCHER_NAME", result = listOf(MatchEntry(GUID1, 0F)), + probeBiometricReferenceId = GUID1, ), OneToOneMatchEvent( createdAt = CREATED_AT, @@ -225,6 +226,7 @@ class EventPayloadTest { matcher = "MATCHER_NAME", result = MatchEntry(GUID1, 0F), fingerComparisonStrategy = FingerComparisonStrategy.SAME_FINGER, + probeBiometricReferenceId = GUID1, ), PersonCreationEvent( startTime = CREATED_AT, diff --git a/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/OneToManyMatchEventTest.kt b/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/OneToManyMatchEventTest.kt index 240dce778a..61ed121139 100644 --- a/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/OneToManyMatchEventTest.kt +++ b/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/OneToManyMatchEventTest.kt @@ -1,10 +1,14 @@ package com.simprints.infra.events.event.domain.models +import com.fasterxml.jackson.core.type.TypeReference import com.google.common.truth.Truth.assertThat +import com.simprints.core.tools.json.JsonHelper import com.simprints.infra.events.event.domain.models.EventType.ONE_TO_MANY_MATCH import com.simprints.infra.events.event.domain.models.OneToManyMatchEvent.Companion.EVENT_VERSION import com.simprints.infra.events.event.domain.models.OneToManyMatchEvent.OneToManyMatchPayload.MatchPool import com.simprints.infra.events.event.domain.models.OneToManyMatchEvent.OneToManyMatchPayload.MatchPoolType.PROJECT +import com.simprints.infra.events.event.domain.models.OneToManyMatchEvent.OneToManyMatchPayload.OneToManyMatchPayloadV2 +import com.simprints.infra.events.event.domain.models.OneToManyMatchEvent.OneToManyMatchPayload.OneToManyMatchPayloadV3 import com.simprints.infra.events.sampledata.SampleDefaults.CREATED_AT import com.simprints.infra.events.sampledata.SampleDefaults.ENDED_AT import com.simprints.infra.events.sampledata.SampleDefaults.GUID1 @@ -15,7 +19,7 @@ class OneToManyMatchEventTest { fun create_OneToManyMatchEvent() { val poolArg = MatchPool(PROJECT, 100) val resultArg = listOf(MatchEntry(GUID1, 0F)) - val event = OneToManyMatchEvent(CREATED_AT, ENDED_AT, poolArg, "MATCHER_NAME", resultArg) + val event = OneToManyMatchEvent(CREATED_AT, ENDED_AT, poolArg, "MATCHER_NAME", resultArg, "referenceId") assertThat(event.id).isNotNull() assertThat(event.type).isEqualTo(ONE_TO_MANY_MATCH) @@ -27,6 +31,69 @@ class OneToManyMatchEventTest { assertThat(matcher).isEqualTo("MATCHER_NAME") assertThat(pool).isEqualTo(poolArg) assertThat(result).isEqualTo(resultArg) + assertThat((this as OneToManyMatchPayloadV3).probeBiometricReferenceId).isEqualTo("referenceId") } } + + @Test + fun shouldParse_v2Event_successfully() { + val actualEvent = JsonHelper.fromJson(oldApiJsonEventString, object : TypeReference() {}) + + assertThat(actualEvent.id).isEqualTo("3afb1b9e-b263-4073-b773-6e1dac20d72f") + assertThat(actualEvent.payload.eventVersion).isEqualTo(2) + assertThat(actualEvent.payload).isInstanceOf(OneToManyMatchPayloadV2::class.java) + } + + @Test + fun shouldParse_v3Event_successfully() { + val actualEvent = JsonHelper.fromJson(newApiJsonEventString, object : TypeReference() {}) + + assertThat(actualEvent.id).isEqualTo("3afb1b9e-b263-4073-b773-6e1dac20d72f") + assertThat(actualEvent.payload.eventVersion).isEqualTo(3) + assertThat(actualEvent.payload).isInstanceOf(OneToManyMatchPayloadV3::class.java) + assertThat((actualEvent.payload as OneToManyMatchPayloadV3).probeBiometricReferenceId).isEqualTo("referenceId") + } + + private val oldApiJsonEventString = + """ + { + "id": "3afb1b9e-b263-4073-b773-6e1dac20d72f", + "scopeId": "6dcb3810-4789-4149-8fea-473ffb520958", + "payload": { + "createdAt": {"ms": 1234}, + "eventVersion": 2, + "pool": { + "type": "PROJECT", + "count": 1040 + }, + "matcher": "SIM_AFIS", + "results": [], + "type": "ONE_TO_MANY_MATCH", + "endedAt": {"ms": 4567} + }, + "type": "ONE_TO_MANY_MATCH" + } + """.trimIndent() + + private val newApiJsonEventString = + """ + { + "id": "3afb1b9e-b263-4073-b773-6e1dac20d72f", + "scopeId": "6dcb3810-4789-4149-8fea-473ffb520958", + "payload": { + "createdAt": {"ms": 1234}, + "eventVersion": 3, + "pool": { + "type": "PROJECT", + "count": 1040 + }, + "matcher": "SIM_AFIS", + "results": [], + "probeBiometricReferenceId": "referenceId", + "type": "ONE_TO_MANY_MATCH", + "endedAt": {"ms": 1234} + }, + "type": "ONE_TO_MANY_MATCH" + } + """.trimIndent() } diff --git a/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/OneToOneMatchEventTest.kt b/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/OneToOneMatchEventTest.kt index 50ebf64720..6309aebbd2 100644 --- a/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/OneToOneMatchEventTest.kt +++ b/infra/events/src/test/java/com/simprints/infra/events/event/domain/models/OneToOneMatchEventTest.kt @@ -1,8 +1,12 @@ package com.simprints.infra.events.event.domain.models +import com.fasterxml.jackson.core.type.TypeReference import com.google.common.truth.Truth.assertThat +import com.simprints.core.tools.json.JsonHelper import com.simprints.infra.events.event.domain.models.EventType.ONE_TO_ONE_MATCH import com.simprints.infra.events.event.domain.models.OneToOneMatchEvent.Companion.EVENT_VERSION +import com.simprints.infra.events.event.domain.models.OneToOneMatchEvent.OneToOneMatchPayload.OneToOneMatchPayloadV3 +import com.simprints.infra.events.event.domain.models.OneToOneMatchEvent.OneToOneMatchPayload.OneToOneMatchPayloadV4 import com.simprints.infra.events.sampledata.SampleDefaults.CREATED_AT import com.simprints.infra.events.sampledata.SampleDefaults.ENDED_AT import com.simprints.infra.events.sampledata.SampleDefaults.GUID1 @@ -19,6 +23,7 @@ class OneToOneMatchEventTest { "MATCHER_NAME", resultArg, FingerComparisonStrategy.SAME_FINGER, + "referenceId", ) assertThat(event.id).isNotNull() @@ -32,6 +37,65 @@ class OneToOneMatchEventTest { .isEqualTo(FingerComparisonStrategy.SAME_FINGER) assertThat(type).isEqualTo(ONE_TO_ONE_MATCH) assertThat(result).isEqualTo(resultArg) + assertThat((this as OneToOneMatchPayloadV4).probeBiometricReferenceId).isEqualTo("referenceId") } } + + @Test + fun shouldParse_v3Event_successfully() { + val actualEvent = JsonHelper.fromJson(oldApiJsonEventString, object : TypeReference() {}) + + assertThat(actualEvent.id).isEqualTo("3afb1b9e-b263-4073-b773-6e1dac20d72f") + assertThat(actualEvent.payload.eventVersion).isEqualTo(3) + assertThat(actualEvent.payload).isInstanceOf(OneToOneMatchPayloadV3::class.java) + } + + @Test + fun shouldParse_v4Event_successfully() { + val actualEvent = JsonHelper.fromJson(newApiJsonEventString, object : TypeReference() {}) + + assertThat(actualEvent.id).isEqualTo("3afb1b9e-b263-4073-b773-6e1dac20d72f") + assertThat(actualEvent.payload.eventVersion).isEqualTo(4) + assertThat(actualEvent.payload).isInstanceOf(OneToOneMatchPayloadV4::class.java) + assertThat((actualEvent.payload as OneToOneMatchPayloadV4).probeBiometricReferenceId).isEqualTo("referenceId") + } + + private val oldApiJsonEventString = + """ + { + "id":"3afb1b9e-b263-4073-b773-6e1dac20d72f", + "scopeId":"6dcb3810-4789-4149-8fea-473ffb520958", + "payload":{ + "createdAt":{"ms":1234}, + "eventVersion":3, + "candidateId":"3afb1b9e-b263-4073-b773-6e1dac20d72f", + "matcher":"SIM_AFIS", + "result":{"candidateId":"3afb1b9e-b263-4073-b773-6e1dac20d72f","score":1.0}, + "fingerComparisonStrategy":"SAME_FINGER", + "type":"ONE_TO_ONE_MATCH", + "endedAt":{"ms":4567} + }, + "type":"ONE_TO_ONE_MATCH" + } + """.trimIndent() + + private val newApiJsonEventString = + """ + { + "id":"3afb1b9e-b263-4073-b773-6e1dac20d72f", + "scopeId":"6dcb3810-4789-4149-8fea-473ffb520958", + "payload":{ + "createdAt":{"ms":1234}, + "eventVersion":4, + "candidateId":"3afb1b9e-b263-4073-b773-6e1dac20d72f", + "matcher":"SIM_AFIS", + "result":{"candidateId":"3afb1b9e-b263-4073-b773-6e1dac20d72f","score":1.0}, + "fingerComparisonStrategy":"SAME_FINGER", + "type":"ONE_TO_ONE_MATCH", + "endedAt":{"ms":4567}, + "probeBiometricReferenceId": "referenceId" + }, + "type":"ONE_TO_ONE_MATCH" + } + """.trimIndent() } From 302f96512381d26f4cb5f0b7e549884317de4470 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 10 Feb 2025 13:52:35 +0200 Subject: [PATCH 08/22] MS-871 Add biometric reference ID to sample models --- .../enrollast/EnrolLastBiometricParams.kt | 2 + .../screen/usecase/BuildSubjectUseCase.kt | 27 ++++++---- .../screen/usecase/BuildSubjectUseCaseTest.kt | 17 +++++-- .../usecases/FingerprintMatcherUseCaseTest.kt | 2 +- .../MapStepsForLastBiometricEnrolUseCase.kt | 2 + ...apStepsForLastBiometricEnrolUseCaseTest.kt | 12 +++-- .../core/domain/common/ReferenceIds.kt | 13 ----- .../simprints/core/domain/face/FaceSample.kt | 1 + .../domain/fingerprint/FingerprintSample.kt | 1 + .../core/domain/common/ReferenceIdsTest.kt | 51 ------------------- .../records/realm/store/config/RealmConfig.kt | 2 +- .../realm/store/models/DbFaceSample.kt | 1 + .../realm/store/models/DbFingerprintSample.kt | 1 + .../records/realm/store/models/DbSubject.kt | 2 - .../commcare/CommCareIdentityDataSource.kt | 2 + .../repository/domain/models/Subject.kt | 2 - .../repository/local/models/DbFaceSample.kt | 2 + .../local/models/DbFingerprintSample.kt | 2 + .../repository/local/models/DbSubject.kt | 2 - .../remote/models/face/ApiFaceReference.kt | 7 +-- .../fingerprint/ApiFingerprintReference.kt | 3 +- .../CommCareIdentityDataSourceTest.kt | 20 +++++++- .../EnrolmentRecordLocalDataSourceImplTest.kt | 4 +- .../repository/local/models/DbSubjectTest.kt | 11 +++- ...EnrolmentRecordRemoteDataSourceImplTest.kt | 3 +- .../sync/down/tasks/SubjectFactory.kt | 23 +++++---- .../sync/down/tasks/SubjectFactoryTest.kt | 8 +++ .../subject/EnrolmentRecordCreationEvent.kt | 6 +-- 28 files changed, 109 insertions(+), 120 deletions(-) diff --git a/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/EnrolLastBiometricParams.kt b/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/EnrolLastBiometricParams.kt index 0e3a6f78e2..12df3a0b14 100644 --- a/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/EnrolLastBiometricParams.kt +++ b/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/EnrolLastBiometricParams.kt @@ -39,12 +39,14 @@ sealed class EnrolLastBiometricStepResult : Parcelable { @Keep @Parcelize data class FingerprintCaptureResult( + val referenceId: String, val results: List, ) : EnrolLastBiometricStepResult() @Keep @Parcelize data class FaceCaptureResult( + val referenceId: String, val results: List, ) : EnrolLastBiometricStepResult() } diff --git a/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/usecase/BuildSubjectUseCase.kt b/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/usecase/BuildSubjectUseCase.kt index 75ab358dd4..1b40ba37b7 100644 --- a/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/usecase/BuildSubjectUseCase.kt +++ b/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/usecase/BuildSubjectUseCase.kt @@ -26,26 +26,30 @@ internal class BuildSubjectUseCase @Inject constructor( params.moduleId, createdAt = Date(timeHelper.now().ms), fingerprintSamples = getFingerprintCaptureResult(params.steps) - ?.map(::fingerprintSample) + ?.let { result -> result.results.map { fingerprintSample(result.referenceId, it) } } + .orEmpty(), + faceSamples = getFaceCaptureResult(params.steps) + ?.let { result -> result.results.map { faceSample(result.referenceId, it) } } .orEmpty(), - faceSamples = getFaceCaptureResult(params.steps)?.map(::faceSample).orEmpty(), ) private fun getFingerprintCaptureResult(steps: List) = steps .filterIsInstance() .firstOrNull() - ?.results private fun getFaceCaptureResult(steps: List) = steps .filterIsInstance() .firstOrNull() - ?.results - private fun fingerprintSample(it: FingerTemplateCaptureResult) = FingerprintSample( - fromDomainToModuleApi(it.finger), - it.template, - it.templateQualityScore, - it.format, + private fun fingerprintSample( + referenceId: String, + result: FingerTemplateCaptureResult, + ) = FingerprintSample( + fromDomainToModuleApi(result.finger), + result.template, + result.templateQualityScore, + result.format, + referenceId, ) private fun fromDomainToModuleApi(finger: Finger) = when (finger) { @@ -61,5 +65,8 @@ internal class BuildSubjectUseCase @Inject constructor( Finger.LEFT_5TH_FINGER -> IFingerIdentifier.LEFT_5TH_FINGER } - private fun faceSample(it: FaceTemplateCaptureResult) = FaceSample(it.template, it.format) + private fun faceSample( + referenceId: String, + result: FaceTemplateCaptureResult, + ) = FaceSample(result.template, result.format, referenceId) } diff --git a/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/usecase/BuildSubjectUseCaseTest.kt b/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/usecase/BuildSubjectUseCaseTest.kt index f337bafdd3..1f71cf4a47 100644 --- a/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/usecase/BuildSubjectUseCaseTest.kt +++ b/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/usecase/BuildSubjectUseCaseTest.kt @@ -68,8 +68,14 @@ class BuildSubjectUseCaseTest { createParams( listOf( EnrolLastBiometricStepResult.FingerprintMatchResult(emptyList(), mockk()), - EnrolLastBiometricStepResult.FingerprintCaptureResult(listOf(mockFingerprintResults(Finger.RIGHT_THUMB))), - EnrolLastBiometricStepResult.FingerprintCaptureResult(listOf(mockFingerprintResults(Finger.LEFT_THUMB))), + EnrolLastBiometricStepResult.FingerprintCaptureResult( + REFERENCE_ID, + listOf(mockFingerprintResults(Finger.RIGHT_THUMB)), + ), + EnrolLastBiometricStepResult.FingerprintCaptureResult( + REFERENCE_ID, + listOf(mockFingerprintResults(Finger.LEFT_THUMB)), + ), ), ), ) @@ -84,6 +90,7 @@ class BuildSubjectUseCaseTest { createParams( listOf( EnrolLastBiometricStepResult.FingerprintCaptureResult( + REFERENCE_ID, listOf( mockFingerprintResults(Finger.RIGHT_5TH_FINGER), mockFingerprintResults(Finger.RIGHT_4TH_FINGER), @@ -111,8 +118,8 @@ class BuildSubjectUseCaseTest { createParams( listOf( EnrolLastBiometricStepResult.FaceMatchResult(emptyList()), - EnrolLastBiometricStepResult.FaceCaptureResult(mockFaceResultsList("first")), - EnrolLastBiometricStepResult.FaceCaptureResult(mockFaceResultsList("second")), + EnrolLastBiometricStepResult.FaceCaptureResult(REFERENCE_ID, mockFaceResultsList("first")), + EnrolLastBiometricStepResult.FaceCaptureResult(REFERENCE_ID, mockFaceResultsList("second")), ), ), ) @@ -133,6 +140,8 @@ class BuildSubjectUseCaseTest { private fun mockFaceResultsList(format: String) = listOf(FaceTemplateCaptureResult(byteArrayOf(), format)) companion object { + private const val REFERENCE_ID = "referenceId" + private const val PROJECT_ID = "projectId" private val USER_ID = "userId".asTokenizableRaw() private val MODULE_ID = "moduleId".asTokenizableRaw() diff --git a/feature/matcher/src/test/java/com/simprints/matcher/usecases/FingerprintMatcherUseCaseTest.kt b/feature/matcher/src/test/java/com/simprints/matcher/usecases/FingerprintMatcherUseCaseTest.kt index e2ac1349de..599f1393b1 100644 --- a/feature/matcher/src/test/java/com/simprints/matcher/usecases/FingerprintMatcherUseCaseTest.kt +++ b/feature/matcher/src/test/java/com/simprints/matcher/usecases/FingerprintMatcherUseCaseTest.kt @@ -182,5 +182,5 @@ internal class FingerprintMatcherUseCaseTest { coVerify { bioSdkWrapper.match(any(), any(), any()) } } - private fun fingerprintSample(finger: IFingerIdentifier) = FingerprintSample(finger, byteArrayOf(1), 42, "format") + private fun fingerprintSample(finger: IFingerIdentifier) = FingerprintSample(finger, byteArrayOf(1), 42, "format", "referenceId") } diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/MapStepsForLastBiometricEnrolUseCase.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/MapStepsForLastBiometricEnrolUseCase.kt index 5c1d48f11b..4996599c5b 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/MapStepsForLastBiometricEnrolUseCase.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/MapStepsForLastBiometricEnrolUseCase.kt @@ -22,6 +22,7 @@ internal class MapStepsForLastBiometricEnrolUseCase @Inject constructor() { ) is FingerprintCaptureResult -> EnrolLastBiometricStepResult.FingerprintCaptureResult( + result.referenceId, result.results.mapNotNull { it.sample }.map { FingerTemplateCaptureResult( it.fingerIdentifier.fromModuleApiToDomain(), @@ -38,6 +39,7 @@ internal class MapStepsForLastBiometricEnrolUseCase @Inject constructor() { ) is FaceCaptureResult -> EnrolLastBiometricStepResult.FaceCaptureResult( + result.referenceId, result.results.mapNotNull { it.sample }.map { FaceTemplateCaptureResult(it.template, it.format) }, ) diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/MapStepsForLastBiometricEnrolUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/MapStepsForLastBiometricEnrolUseCaseTest.kt index e3566b7f73..eb4f9cdbc4 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/MapStepsForLastBiometricEnrolUseCaseTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/MapStepsForLastBiometricEnrolUseCaseTest.kt @@ -51,7 +51,7 @@ internal class MapStepsForLastBiometricEnrolUseCaseTest { val result = useCase( listOf( FaceCaptureResult( - "", + "referenceId", results = listOf( FaceCaptureResult.Item(captureEventId = null, index = 0, sample = null), FaceCaptureResult.Item( @@ -71,8 +71,9 @@ internal class MapStepsForLastBiometricEnrolUseCaseTest { assertThat(result.first()).isEqualTo( EnrolLastBiometricStepResult.FaceCaptureResult( - listOf( - FaceTemplateCaptureResult( + referenceId = "referenceId", + results = listOf( + element = FaceTemplateCaptureResult( template = byteArrayOf(), format = "format", ), @@ -99,7 +100,7 @@ internal class MapStepsForLastBiometricEnrolUseCaseTest { val result = useCase( listOf( FingerprintCaptureResult( - "", + "referenceId", results = listOf( FingerprintCaptureResult.Item(null, IFingerIdentifier.LEFT_THUMB, null), FingerprintCaptureResult.Item( @@ -120,7 +121,8 @@ internal class MapStepsForLastBiometricEnrolUseCaseTest { assertThat(result.first()).isEqualTo( EnrolLastBiometricStepResult.FingerprintCaptureResult( - listOf( + referenceId = "referenceId", + results = listOf( FingerTemplateCaptureResult( template = byteArrayOf(), templateQualityScore = 0, diff --git a/infra/core/src/main/java/com/simprints/core/domain/common/ReferenceIds.kt b/infra/core/src/main/java/com/simprints/core/domain/common/ReferenceIds.kt index 27e7b7e716..bb8bbbe749 100644 --- a/infra/core/src/main/java/com/simprints/core/domain/common/ReferenceIds.kt +++ b/infra/core/src/main/java/com/simprints/core/domain/common/ReferenceIds.kt @@ -1,7 +1,5 @@ package com.simprints.core.domain.common -import com.simprints.core.domain.face.FaceSample -import com.simprints.core.domain.fingerprint.FingerprintSample import java.util.UUID /** @@ -15,11 +13,6 @@ fun List.faceReferenceId(): String? = if (isNotEmpty()) { null } -/** - * Generate UUID based on the provided face samples. - */ -fun List.faceReferenceIdFromSamples(): String? = map { it.template }.faceReferenceId() - /** * Generate UUID based on the provided fingerprint templates and template quality scores. */ @@ -30,9 +23,3 @@ fun List>.fingerprintReferenceId(): String? = if (isNotEmpt } else { null } - -/** - * Generate UUID based on the provided fingerprint templates. - */ -fun List.fingerprintReferenceIdFromSamples(): String? = - map { it.templateQualityScore to it.template }.fingerprintReferenceId() diff --git a/infra/core/src/main/java/com/simprints/core/domain/face/FaceSample.kt b/infra/core/src/main/java/com/simprints/core/domain/face/FaceSample.kt index 1ddae8e170..fbf1938b6b 100644 --- a/infra/core/src/main/java/com/simprints/core/domain/face/FaceSample.kt +++ b/infra/core/src/main/java/com/simprints/core/domain/face/FaceSample.kt @@ -10,6 +10,7 @@ import java.util.UUID data class FaceSample( val template: ByteArray, val format: String, + val referenceId: String, val id: String = UUID.randomUUID().toString(), ) : Parcelable { override fun equals(other: Any?): Boolean { diff --git a/infra/core/src/main/java/com/simprints/core/domain/fingerprint/FingerprintSample.kt b/infra/core/src/main/java/com/simprints/core/domain/fingerprint/FingerprintSample.kt index ccfdb00a97..5504dfa29b 100644 --- a/infra/core/src/main/java/com/simprints/core/domain/fingerprint/FingerprintSample.kt +++ b/infra/core/src/main/java/com/simprints/core/domain/fingerprint/FingerprintSample.kt @@ -12,6 +12,7 @@ data class FingerprintSample( val template: ByteArray, val templateQualityScore: Int, val format: String, + val referenceId: String, val id: String = UUID.randomUUID().toString(), ) : Parcelable { override fun equals(other: Any?): Boolean { diff --git a/infra/core/src/test/java/com/simprints/core/domain/common/ReferenceIdsTest.kt b/infra/core/src/test/java/com/simprints/core/domain/common/ReferenceIdsTest.kt index a8196fded1..ea2b7e6841 100644 --- a/infra/core/src/test/java/com/simprints/core/domain/common/ReferenceIdsTest.kt +++ b/infra/core/src/test/java/com/simprints/core/domain/common/ReferenceIdsTest.kt @@ -1,9 +1,6 @@ package com.simprints.core.domain.common import com.google.common.truth.Truth.assertThat -import com.simprints.core.domain.face.FaceSample -import com.simprints.core.domain.fingerprint.FingerprintSample -import com.simprints.core.domain.fingerprint.IFingerIdentifier import org.junit.Test import java.util.UUID import kotlin.collections.listOf @@ -21,27 +18,6 @@ class ReferenceIdsTest { assertThat(templates.faceReferenceId()).isEqualTo(expected) } - @Test - fun `face reference ID from samples is calculated correctly`() { - val samples = listOf( - FaceSample( - template = byteArrayOf(2), - format = "", - ), - FaceSample( - template = byteArrayOf(3), - format = "", - ), - FaceSample( - template = byteArrayOf(1), - format = "", - ), - ) - val expected = UUID.nameUUIDFromBytes(byteArrayOf(1, 2, 3)).toString() - - assertThat(samples.faceReferenceIdFromSamples()).isEqualTo(expected) - } - @Test fun `face reference ID returns null for empty list`() { assertThat(listOf().faceReferenceId()).isNull() @@ -59,33 +35,6 @@ class ReferenceIdsTest { assertThat(samples.fingerprintReferenceId()).isEqualTo(expected) } - @Test - fun `fingerprint reference ID from samples calculated from correctly`() { - val samples = listOf( - FingerprintSample( - fingerIdentifier = IFingerIdentifier.LEFT_3RD_FINGER, - template = byteArrayOf(31, 32), - templateQualityScore = 3, - format = "", - ), - FingerprintSample( - fingerIdentifier = IFingerIdentifier.LEFT_3RD_FINGER, - template = byteArrayOf(1, 2), - templateQualityScore = 1, - format = "", - ), - FingerprintSample( - fingerIdentifier = IFingerIdentifier.LEFT_3RD_FINGER, - template = byteArrayOf(21, 22), - templateQualityScore = 2, - format = "", - ), - ) - val expected = UUID.nameUUIDFromBytes(byteArrayOf(1, 2, 21, 22, 31, 32)).toString() - - assertThat(samples.fingerprintReferenceIdFromSamples()).isEqualTo(expected) - } - @Test fun `fingerprint reference ID returns null for empty list`() { assertThat(listOf>().fingerprintReferenceId()).isNull() diff --git a/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/config/RealmConfig.kt b/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/config/RealmConfig.kt index 4c01402903..c23eb21c38 100644 --- a/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/config/RealmConfig.kt +++ b/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/config/RealmConfig.kt @@ -36,6 +36,6 @@ class RealmConfig @Inject constructor() { .build() companion object { - private const val REALM_SCHEMA_VERSION: Long = 15 + private const val REALM_SCHEMA_VERSION: Long = 16 } } diff --git a/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbFaceSample.kt b/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbFaceSample.kt index b8b85674eb..60e7bff875 100644 --- a/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbFaceSample.kt +++ b/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbFaceSample.kt @@ -10,6 +10,7 @@ import io.realm.kotlin.types.annotations.PrimaryKey class DbFaceSample : RealmObject { @PrimaryKey var id: String = "" + var referenceId = "" var template: ByteArray = byteArrayOf() var format: String = "" } diff --git a/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbFingerprintSample.kt b/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbFingerprintSample.kt index cb109a80f6..435ce2fec8 100644 --- a/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbFingerprintSample.kt +++ b/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbFingerprintSample.kt @@ -10,6 +10,7 @@ import io.realm.kotlin.types.annotations.PrimaryKey class DbFingerprintSample : RealmObject { @PrimaryKey var id: String = "" + var referenceId = "" var fingerIdentifier: Int = -1 var template: ByteArray = byteArrayOf() var templateQualityScore: Int = -1 diff --git a/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbSubject.kt b/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbSubject.kt index ff507dec32..612e8bb8a7 100644 --- a/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbSubject.kt +++ b/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbSubject.kt @@ -23,8 +23,6 @@ class DbSubject : RealmObject { var fingerprintSamples: RealmList = realmListOf() var faceSamples: RealmList = realmListOf() - @Deprecated("See SubjectToEventDbMigrationManagerImpl doc") - var toSync: Boolean = false var isAttendantIdTokenized: Boolean = false var isModuleIdTokenized: Boolean = false } diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/commcare/CommCareIdentityDataSource.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/commcare/CommCareIdentityDataSource.kt index 1f204c9f9e..cee89e791b 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/commcare/CommCareIdentityDataSource.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/commcare/CommCareIdentityDataSource.kt @@ -67,6 +67,7 @@ internal class CommCareIdentityDataSource @Inject constructor( templateQualityScore = fingerprintTemplate.quality, template = encoder.base64ToBytes(fingerprintTemplate.template), format = fingerprintReference.format, + referenceId = fingerprintReference.id, ) } }, @@ -144,6 +145,7 @@ internal class CommCareIdentityDataSource @Inject constructor( FaceSample( template = encoder.base64ToBytes(faceTemplate.template), format = faceReference.format, + referenceId = faceReference.id, ) } }, diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/Subject.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/Subject.kt index 9270a5c566..23d307709b 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/Subject.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/Subject.kt @@ -17,6 +17,4 @@ data class Subject( val updatedAt: Date? = null, var fingerprintSamples: List = emptyList(), var faceSamples: List = emptyList(), - @Deprecated("See SubjectToEventDbMigrationManagerImpl doc") - val toSync: Boolean = false, ) : Parcelable diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/DbFaceSample.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/DbFaceSample.kt index e115efce86..00ef14df71 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/DbFaceSample.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/DbFaceSample.kt @@ -7,10 +7,12 @@ internal fun DbFaceSample.fromDbToDomain(): FaceSample = FaceSample( id = id, template = template, format = format, + referenceId = referenceId, ) internal fun FaceSample.fromDomainToDb(): DbFaceSample = DbFaceSample().also { sample -> sample.id = id + sample.referenceId = referenceId sample.template = template sample.format = format } diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/DbFingerprintSample.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/DbFingerprintSample.kt index 74acd52c72..6c7c248558 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/DbFingerprintSample.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/DbFingerprintSample.kt @@ -10,10 +10,12 @@ internal fun DbFingerprintSample.fromDbToDomain(): FingerprintSample = Fingerpri template = template, templateQualityScore = templateQualityScore, format = format, + referenceId = referenceId, ) internal fun FingerprintSample.fromDomainToDb(): DbFingerprintSample = DbFingerprintSample().also { sample -> sample.id = id + sample.referenceId = referenceId sample.fingerIdentifier = fingerIdentifier.ordinal sample.template = template sample.templateQualityScore = templateQualityScore diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/DbSubject.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/DbSubject.kt index daaa1be6c0..7cace06d8c 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/DbSubject.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/DbSubject.kt @@ -27,7 +27,6 @@ internal fun DbSubject.fromDbToDomain(): Subject { moduleId = moduleId, createdAt = createdAt?.toDate(), updatedAt = updatedAt?.toDate(), - toSync = toSync, fingerprintSamples = fingerprintSamples.map(DbFingerprintSample::fromDbToDomain), faceSamples = faceSamples.map(DbFaceSample::fromDbToDomain), ) @@ -40,7 +39,6 @@ internal fun Subject.fromDomainToDb(): DbSubject = DbSubject().also { subject -> subject.moduleId = moduleId.value subject.createdAt = createdAt?.toRealmInstant() subject.updatedAt = updatedAt?.toRealmInstant() - subject.toSync = toSync subject.fingerprintSamples = fingerprintSamples.map(FingerprintSample::fromDomainToDb).toRealmList() subject.faceSamples = faceSamples.map(FaceSample::fromDomainToDb).toRealmList() diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/face/ApiFaceReference.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/face/ApiFaceReference.kt index 173b06d6db..6eb6e5f811 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/face/ApiFaceReference.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/face/ApiFaceReference.kt @@ -1,7 +1,6 @@ package com.simprints.infra.enrolment.records.repository.remote.models.face import androidx.annotation.Keep -import com.simprints.core.domain.common.faceReferenceIdFromSamples import com.simprints.core.domain.face.FaceSample import com.simprints.core.tools.utils.EncodingUtils import com.simprints.infra.enrolment.records.repository.remote.models.ApiBiometricReference @@ -16,10 +15,8 @@ internal data class ApiFaceReference( internal fun List.toApi(encoder: EncodingUtils): ApiFaceReference? = if (isNotEmpty()) { ApiFaceReference( - faceReferenceIdFromSamples().orEmpty(), - map { - ApiFaceTemplate(encoder.byteArrayToBase64(it.template)) - }, + first().referenceId, + map { ApiFaceTemplate(encoder.byteArrayToBase64(it.template)) }, first().format, ) } else { diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/fingerprint/ApiFingerprintReference.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/fingerprint/ApiFingerprintReference.kt index 52151bff72..826679e85c 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/fingerprint/ApiFingerprintReference.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/remote/models/fingerprint/ApiFingerprintReference.kt @@ -1,7 +1,6 @@ package com.simprints.infra.enrolment.records.repository.remote.models.fingerprint import androidx.annotation.Keep -import com.simprints.core.domain.common.fingerprintReferenceIdFromSamples import com.simprints.core.domain.fingerprint.FingerprintSample import com.simprints.core.tools.utils.EncodingUtils import com.simprints.infra.enrolment.records.repository.remote.models.ApiBiometricReference @@ -16,7 +15,7 @@ internal data class ApiFingerprintReference( internal fun List.toApi(encoder: EncodingUtils): ApiFingerprintReference? = if (isNotEmpty()) { ApiFingerprintReference( - fingerprintReferenceIdFromSamples() ?: "", + first().referenceId, map { ApiFingerprintTemplate( it.templateQualityScore, diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/commcare/CommCareIdentityDataSourceTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/commcare/CommCareIdentityDataSourceTest.kt index 6ed9d38faa..33c4cafc41 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/commcare/CommCareIdentityDataSourceTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/commcare/CommCareIdentityDataSourceTest.kt @@ -55,12 +55,14 @@ class CommCareIdentityDataSourceTest { templateQualityScore = 99, template = byteArrayOf(), format = "ISO_19794_2", + referenceId = "referenceId", ), FingerprintSample( fingerIdentifier = LEFT_INDEX_FINGER, templateQualityScore = 88, template = byteArrayOf(), format = "ISO_19794_2", + referenceId = "referenceId", ), ), ), @@ -72,12 +74,14 @@ class CommCareIdentityDataSourceTest { templateQualityScore = 77, template = byteArrayOf(), format = "ISO_19794_2", + referenceId = "referenceId", ), FingerprintSample( fingerIdentifier = LEFT_INDEX_FINGER, templateQualityScore = 66, template = byteArrayOf(), format = "ISO_19794_2", + referenceId = "referenceId", ), ), ), @@ -85,11 +89,23 @@ class CommCareIdentityDataSourceTest { val expectedFaceIdentities = listOf( FaceIdentity( subjectId = "b26c91bc-b307-4131-80c3-55090ba5dbf2", - faces = listOf(FaceSample(template = byteArrayOf(), format = "ROC_1_23")), + faces = listOf( + FaceSample( + template = byteArrayOf(), + format = "ROC_1_23", + referenceId = "referenceId", + ), + ), ), FaceIdentity( subjectId = "a961fcb4-8573-4270-a1b2-088e88275b00", - faces = listOf(FaceSample(template = byteArrayOf(), format = "ROC_3")), + faces = listOf( + FaceSample( + template = byteArrayOf(), + format = "ROC_3", + referenceId = "referenceId", + ), + ), ), ) diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt index 0ee8f8f721..be2c16f205 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt @@ -308,8 +308,8 @@ class EnrolmentRecordLocalDataSourceImplTest { userId: String = UUID.randomUUID().toString(), moduleId: String = UUID.randomUUID().toString(), faceSamples: Array = arrayOf( - FaceSample(Random.nextBytes(64), "faceTemplateFormat"), - FaceSample(Random.nextBytes(64), "faceTemplateFormat"), + FaceSample(Random.nextBytes(64), "faceTemplateFormat", "referenceId"), + FaceSample(Random.nextBytes(64), "faceTemplateFormat", "referenceId"), ), ): Subject = Subject( subjectId = patientId, diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/models/DbSubjectTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/models/DbSubjectTest.kt index e4a6e223cf..048cae41d2 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/models/DbSubjectTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/models/DbSubjectTest.kt @@ -20,6 +20,7 @@ class DbSubjectTest { companion object { private const val GUID = "3f0f8e9a-0a0c-456c-846e-577b1440b6fb" private const val PROJECT_ID = "projectId" + private const val REFERENCE_ID = "referenceId" private val ATTENDANT_ID = "user1".asTokenizableEncrypted() private val MODULE_ID = "module".asTokenizableEncrypted() } @@ -31,8 +32,9 @@ class DbSubjectTest { Random.nextBytes(64), 30, "NEC_1", + REFERENCE_ID, ) - val faceSample = FaceSample(Random.nextBytes(64), "RANK_ONE_1_23") + val faceSample = FaceSample(Random.nextBytes(64), "RANK_ONE_1_23", REFERENCE_ID) val domainSubject = Subject( subjectId = GUID, @@ -55,7 +57,9 @@ class DbSubjectTest { assertThat(createdAt).isEqualTo(RealmInstant.from(0, 0)) assertThat(updatedAt).isEqualTo(RealmInstant.from(1, 500_000_000)) assertThat(fingerprintSamples.first().id).isEqualTo(fingerprintSample.id) + assertThat(fingerprintSamples.first().referenceId).isEqualTo(REFERENCE_ID) assertThat(faceSamples.first().id).isEqualTo(faceSample.id) + assertThat(faceSamples.first().referenceId).isEqualTo(REFERENCE_ID) } } @@ -66,10 +70,12 @@ class DbSubjectTest { template = Random.nextBytes(64) templateQualityScore = 30 format = "NEC_1" + referenceId = REFERENCE_ID } val faceSample = DbFaceSample().apply { template = Random.nextBytes(64) format = "RANK_ONE_1_23" + referenceId = REFERENCE_ID } val dbSubject = DbSubject().apply { @@ -95,7 +101,8 @@ class DbSubjectTest { assertThat(moduleId).isEqualTo(MODULE_ID) assertThat(projectId).isEqualTo(PROJECT_ID) assertThat(fingerprintSamples.first().id).isEqualTo(fingerprintSample.id) - assertThat(faceSamples.first().id).isEqualTo(faceSample.id) + assertThat(fingerprintSamples.first().referenceId).isEqualTo(REFERENCE_ID) + assertThat(faceSamples.first().referenceId).isEqualTo(REFERENCE_ID) } } } diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/remote/EnrolmentRecordRemoteDataSourceImplTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/remote/EnrolmentRecordRemoteDataSourceImplTest.kt index 4d267117e3..70c8e072b5 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/remote/EnrolmentRecordRemoteDataSourceImplTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/remote/EnrolmentRecordRemoteDataSourceImplTest.kt @@ -77,9 +77,10 @@ class EnrolmentRecordRemoteDataSourceImplTest { FINGERPRINT_TEMPLATE, 50, "ISO_19794_2", + "5289df73-7df5-3326-bcdd-22597afb1fac", ), ), - faceSamples = listOf(FaceSample(FACE_TEMPLATE, "faceTemplateFormat")), + faceSamples = listOf(FaceSample(FACE_TEMPLATE, "faceTemplateFormat", "b4a3ba90-6413-32b4-a4ea-a841a5a400ec")), ) val expectedRecord = ApiEnrolmentRecord( subjectId = SUBJECT_ID, diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt index 1614518e8f..c8f9f76008 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt @@ -92,45 +92,46 @@ class SubjectFactory @Inject constructor( sample.template, sample.templateQualityScore, sample.format, + fingerprintResponse.referenceId, ) } } private fun extractFaceSamples(faceResponse: FaceCaptureResult) = faceResponse.results .mapNotNull { it.sample } - .map { FaceSample(it.template, it.format) } + .map { FaceSample(it.template, it.format, faceResponse.referenceId) } private fun extractFingerprintSamplesFromBiometricReferences(biometricReferences: List?) = biometricReferences ?.filterIsInstance() ?.firstOrNull() - ?.let { reference -> - reference.templates.map { - buildFingerprintSample( - it, - reference.format, - ) - } - } + ?.let { reference -> reference.templates.map { buildFingerprintSample(it, reference.format, reference.id) } } ?: emptyList() private fun buildFingerprintSample( template: FingerprintTemplate, format: String, + referenceId: String, ): FingerprintSample = FingerprintSample( fingerIdentifier = template.finger, template = encodingUtils.base64ToBytes(template.template), templateQualityScore = template.quality, format = format, + referenceId = referenceId, ) private fun extractFaceSamplesFromBiometricReferences(biometricReferences: List?) = biometricReferences ?.filterIsInstance() ?.firstOrNull() - ?.let { reference -> reference.templates.map { buildFaceSample(it, reference.format) } } + ?.let { reference -> reference.templates.map { buildFaceSample(it, reference.format, reference.id) } } ?: emptyList() private fun buildFaceSample( template: FaceTemplate, format: String, - ) = FaceSample(encodingUtils.base64ToBytes(template.template), format) + referenceId: String, + ) = FaceSample( + template = encodingUtils.base64ToBytes(template.template), + format = format, + referenceId = referenceId, + ) } diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactoryTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactoryTest.kt index d96e76033d..a957f3add1 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactoryTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactoryTest.kt @@ -73,12 +73,14 @@ class SubjectFactoryTest { template = BASE_64_BYTES, templateQualityScore = QUALITY, format = REFERENCE_FORMAT, + referenceId = REFERENCE_ID, ), ), faceSamples = listOf( FaceSample( template = BASE_64_BYTES, format = REFERENCE_FORMAT, + referenceId = REFERENCE_ID, ), ), ) @@ -107,12 +109,14 @@ class SubjectFactoryTest { template = BASE_64_BYTES, templateQualityScore = QUALITY, format = REFERENCE_FORMAT, + referenceId = REFERENCE_ID, ), ), faceSamples = listOf( FaceSample( template = BASE_64_BYTES, format = REFERENCE_FORMAT, + referenceId = REFERENCE_ID, ), ), ) @@ -135,12 +139,14 @@ class SubjectFactoryTest { template = BASE_64_BYTES, templateQualityScore = QUALITY, format = REFERENCE_FORMAT, + referenceId = REFERENCE_ID, ), ), faceSamples = listOf( FaceSample( template = BASE_64_BYTES, format = REFERENCE_FORMAT, + referenceId = REFERENCE_ID, ), ), ) @@ -197,12 +203,14 @@ class SubjectFactoryTest { template = BASE_64_BYTES, templateQualityScore = QUALITY, format = REFERENCE_FORMAT, + referenceId = REFERENCE_ID, ), ), faceSamples = listOf( FaceSample( template = BASE_64_BYTES, format = REFERENCE_FORMAT, + referenceId = REFERENCE_ID, ), ), ) diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordCreationEvent.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordCreationEvent.kt index 4964be851c..c976823440 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordCreationEvent.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordCreationEvent.kt @@ -1,8 +1,6 @@ package com.simprints.infra.events.event.domain.models.subject import androidx.annotation.Keep -import com.simprints.core.domain.common.faceReferenceIdFromSamples -import com.simprints.core.domain.common.fingerprintReferenceIdFromSamples import com.simprints.core.domain.face.FaceSample import com.simprints.core.domain.fingerprint.FingerprintSample import com.simprints.core.domain.tokenization.TokenizableString @@ -64,7 +62,7 @@ data class EnrolmentRecordCreationEvent( encoder: EncodingUtils, ) = if (fingerprintSamples.isNotEmpty()) { FingerprintReference( - fingerprintSamples.fingerprintReferenceIdFromSamples() ?: "", + fingerprintSamples.first().referenceId, fingerprintSamples.map { FingerprintTemplate( it.templateQualityScore, @@ -83,7 +81,7 @@ data class EnrolmentRecordCreationEvent( encoder: EncodingUtils, ) = if (faceSamples.isNotEmpty()) { FaceReference( - faceSamples.faceReferenceIdFromSamples() ?: "", + faceSamples.first().referenceId, faceSamples.map { FaceTemplate( encoder.byteArrayToBase64(it.template), From e3c5d911101442c3b6f2ea820798854d13d91642 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 10 Feb 2025 14:10:30 +0200 Subject: [PATCH 09/22] MS-871 Deprecating PersonCreationEvent --- .../orchestrator/OrchestratorViewModel.kt | 9 - .../usecases/CreatePersonEventUseCase.kt | 74 ---- .../usecases/ShouldCreatePersonUseCase.kt | 72 ---- .../orchestrator/OrchestratorViewModelTest.kt | 29 -- .../usecases/CreatePersonEventUseCaseTest.kt | 190 --------- .../usecases/ShouldCreatePersonUseCaseTest.kt | 392 ------------------ .../remote/models/ApiPersonCreationPayload.kt | 1 + .../domain/models/PersonCreationEvent.kt | 2 + .../PersonCreationEventValidator.kt | 29 -- .../SessionEventValidatorsFactory.kt | 1 - 10 files changed, 3 insertions(+), 796 deletions(-) delete mode 100644 feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCase.kt delete mode 100644 feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/ShouldCreatePersonUseCase.kt delete mode 100644 feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCaseTest.kt delete mode 100644 feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/ShouldCreatePersonUseCaseTest.kt delete mode 100644 infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/PersonCreationEventValidator.kt diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt index 3688d0b6a8..342c0025f8 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/OrchestratorViewModel.kt @@ -24,10 +24,8 @@ 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.orchestrator.usecases.AddCallbackEventUseCase -import com.simprints.feature.orchestrator.usecases.CreatePersonEventUseCase import com.simprints.feature.orchestrator.usecases.MapRefusalOrErrorResultUseCase import com.simprints.feature.orchestrator.usecases.MapStepsForLastBiometricEnrolUseCase -import com.simprints.feature.orchestrator.usecases.ShouldCreatePersonUseCase import com.simprints.feature.orchestrator.usecases.UpdateDailyActivityUseCase import com.simprints.feature.orchestrator.usecases.response.AppResponseBuilderUseCase import com.simprints.feature.orchestrator.usecases.steps.BuildStepsUseCase @@ -55,8 +53,6 @@ internal class OrchestratorViewModel @Inject constructor( private val locationStore: LocationStore, private val stepsBuilder: BuildStepsUseCase, private val mapRefusalOrErrorResult: MapRefusalOrErrorResultUseCase, - private val shouldCreatePerson: ShouldCreatePersonUseCase, - private val createPersonEvent: CreatePersonEventUseCase, private val appResponseBuilder: AppResponseBuilderUseCase, private val addCallbackEvent: AddCallbackEventUseCase, private val updateDailyActivity: UpdateDailyActivityUseCase, @@ -115,11 +111,6 @@ internal class OrchestratorViewModel @Inject constructor( updateMatcherStepPayload(step, result) } - if (shouldCreatePerson(actionRequest, modalities, steps)) { - Simber.i("Creating person event", tag = ORCHESTRATION) - createPersonEvent(steps.mapNotNull { it.result }) - } - if (result is SelectSubjectAgeGroupResult) { val captureAndMatchSteps = stepsBuilder.buildCaptureAndMatchStepsForAgeGroup( actionRequest!!, diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCase.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCase.kt deleted file mode 100644 index 4f11603e3e..0000000000 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCase.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.simprints.feature.orchestrator.usecases - -import com.simprints.core.domain.common.faceReferenceId -import com.simprints.core.domain.common.fingerprintReferenceId -import com.simprints.core.tools.time.TimeHelper -import com.simprints.face.capture.FaceCaptureResult -import com.simprints.fingerprint.capture.FingerprintCaptureResult -import com.simprints.infra.events.event.domain.models.PersonCreationEvent -import com.simprints.infra.events.session.SessionEventRepository -import java.io.Serializable -import javax.inject.Inject - -internal class CreatePersonEventUseCase @Inject constructor( - private val eventRepository: SessionEventRepository, - private val timeHelper: TimeHelper, -) { - suspend operator fun invoke(results: List) { - val sessionEvents = eventRepository.getEventsInCurrentSession() - val previousPersonCreationEvent = sessionEvents - .filterIsInstance() - .sortedByDescending { it.payload.createdAt } - .firstOrNull() - - val faceCaptures = extractFaceCaptures(results) - val fingerprintCaptures = extractFingerprintCaptures(results) - - val personCreationEvent = build(faceCaptures, fingerprintCaptures, previousPersonCreationEvent) - - if (personCreationEvent.hasBiometricData()) { - eventRepository.addOrUpdateEvent(personCreationEvent) - } - } - - private fun extractFaceCaptures(responses: List) = responses - .filterIsInstance() - .flatMap { it.results } - - private fun extractFingerprintCaptures(responses: List) = responses - .filterIsInstance() - .flatMap { it.results } - - private fun build( - faceSamplesForPersonCreation: List, - fingerprintSamplesForPersonCreation: List, - previousPersonCreationEvent: PersonCreationEvent? = null, - ): PersonCreationEvent { - val fingerprintCaptureIds = fingerprintSamplesForPersonCreation - .mapNotNull { it.captureEventId } - .ifEmpty { null } - val fingerprintReferenceId = fingerprintSamplesForPersonCreation - .mapNotNull { it.sample } - .map { it.templateQualityScore to it.template } - .fingerprintReferenceId() - - val faceCaptureIds = faceSamplesForPersonCreation - .mapNotNull { it.captureEventId } - .ifEmpty { null } - val faceReferenceId = faceSamplesForPersonCreation - .mapNotNull { it.sample?.template } - .faceReferenceId() - - // If the step results of the current callout do not contain a modality but we have a PersonCreationEvent from the - // previous callout (of the same session), we use the modality from the previous callout. This happens when the - // user skips a modality during identification (due to matching modalities configuration) and then captures the - // skipped modality in the following enrol last callout. - return PersonCreationEvent( - startTime = timeHelper.now(), - fingerprintCaptureIds = fingerprintCaptureIds ?: previousPersonCreationEvent?.payload?.fingerprintCaptureIds, - fingerprintReferenceId = fingerprintReferenceId ?: previousPersonCreationEvent?.payload?.fingerprintReferenceId, - faceCaptureIds = faceCaptureIds ?: previousPersonCreationEvent?.payload?.faceCaptureIds, - faceReferenceId = faceReferenceId ?: previousPersonCreationEvent?.payload?.faceReferenceId, - ) - } -} diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/ShouldCreatePersonUseCase.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/ShouldCreatePersonUseCase.kt deleted file mode 100644 index 1b69babb11..0000000000 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/ShouldCreatePersonUseCase.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.simprints.feature.orchestrator.usecases - -import com.simprints.face.capture.FaceCaptureResult -import com.simprints.feature.orchestrator.steps.Step -import com.simprints.feature.orchestrator.steps.StepId -import com.simprints.fingerprint.capture.FingerprintCaptureResult -import com.simprints.infra.config.store.models.GeneralConfiguration -import com.simprints.infra.events.event.domain.models.PersonCreationEvent -import com.simprints.infra.events.session.SessionEventRepository -import com.simprints.infra.logging.LoggingConstants.CrashReportTag.ORCHESTRATION -import com.simprints.infra.logging.Simber -import com.simprints.infra.orchestration.data.ActionRequest -import javax.inject.Inject - -internal class ShouldCreatePersonUseCase @Inject constructor( - private val eventRepository: SessionEventRepository, -) { - suspend operator fun invoke( - actionRequest: ActionRequest?, - modalities: Set, - results: List, - ): Boolean { - if (actionRequest !is ActionRequest.FlowAction && - actionRequest !is ActionRequest.EnrolLastBiometricActionRequest - ) { - return false - } - - if (modalities.isEmpty()) { - Simber.i("Could not create person event - modalities are empty", tag = ORCHESTRATION) - return false - } - - val faceCaptureResults = results.filter { it.id == StepId.FACE_CAPTURE } - val fingerprintCaptureResults = results.filter { it.id == StepId.FINGERPRINT_CAPTURE } - val faceCaptureIsScheduled = faceCaptureResults.isNotEmpty() - val fingerprintCaptureIsScheduled = fingerprintCaptureResults.isNotEmpty() - val faceCaptureIsComplete = faceCaptureResults.all { it.result is FaceCaptureResult } - val fingerprintCaptureIsComplete = fingerprintCaptureResults.all { it.result is FingerprintCaptureResult } - - // Neither face nor fingerprint capture is scheduled so no captures for a PersonCreation event - if (!faceCaptureIsScheduled && !fingerprintCaptureIsScheduled) { - return false - } - - // There are scheduled captures but not all of them are complete - if (!faceCaptureIsComplete || !fingerprintCaptureIsComplete) { - return false - } - - val sessionEvents = eventRepository.getEventsInCurrentSession() - val personCreationEvents = sessionEvents.filterIsInstance() - // We already have the maximum number of PersonCreation events (2) in the session - if (personCreationEvents.size > 1) { - return false - } - - val currentPersonCreationEvent = personCreationEvents.firstOrNull() - // If all scheduled capture steps are complete and we don't yet have a PersonCreation event, - // we should create one - if (currentPersonCreationEvent == null) { - return true - } - - // If we have a PersonCreation event but it's missing a reference that was scheduled for capture - // we should create a new one. This happens when an identification with only one modality is performed - // (due to matching modalities configuration) and the other modality is scheduled for capture in the - // following enrol last request - return (faceCaptureIsScheduled && !currentPersonCreationEvent.hasFaceReference()) || - (fingerprintCaptureIsScheduled && !currentPersonCreationEvent.hasFingerprintReference()) - } -} diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/OrchestratorViewModelTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/OrchestratorViewModelTest.kt index 2095efc377..da5b09c520 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/OrchestratorViewModelTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/OrchestratorViewModelTest.kt @@ -20,10 +20,8 @@ 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.orchestrator.usecases.AddCallbackEventUseCase -import com.simprints.feature.orchestrator.usecases.CreatePersonEventUseCase import com.simprints.feature.orchestrator.usecases.MapRefusalOrErrorResultUseCase import com.simprints.feature.orchestrator.usecases.MapStepsForLastBiometricEnrolUseCase -import com.simprints.feature.orchestrator.usecases.ShouldCreatePersonUseCase import com.simprints.feature.orchestrator.usecases.UpdateDailyActivityUseCase import com.simprints.feature.orchestrator.usecases.response.AppResponseBuilderUseCase import com.simprints.feature.orchestrator.usecases.steps.BuildStepsUseCase @@ -81,12 +79,6 @@ internal class OrchestratorViewModelTest { @MockK private lateinit var mapRefusalOrErrorResult: MapRefusalOrErrorResultUseCase - @MockK - private lateinit var shouldCreatePerson: ShouldCreatePersonUseCase - - @MockK - private lateinit var createPersonEvent: CreatePersonEventUseCase - @MockK private lateinit var appResponseBuilder: AppResponseBuilderUseCase @@ -111,8 +103,6 @@ internal class OrchestratorViewModelTest { locationStore, stepsBuilder, mapRefusalOrErrorResult, - shouldCreatePerson, - createPersonEvent, appResponseBuilder, addCallbackEvent, dailyActivityUseCase, @@ -140,7 +130,6 @@ internal class OrchestratorViewModelTest { createMockStep(StepId.CONSENT), ) coEvery { mapRefusalOrErrorResult(any(), any()) } returns null - coEvery { shouldCreatePerson(any(), any(), any()) } returns false val stepsObserver = viewModel.currentStep.test() @@ -151,19 +140,6 @@ internal class OrchestratorViewModelTest { .isEqualTo(listOf(StepId.SETUP, StepId.CONSENT)) } - @Test - fun `Creates person if required after step result`() = runTest { - every { stepsBuilder.build(any(), any()) } returns emptyList() - coEvery { mapRefusalOrErrorResult(any(), any()) } returns null - - coEvery { shouldCreatePerson(any(), any(), any()) } returns true - coJustRun { createPersonEvent(any()) } - - viewModel.handleResult(SetupResult(true)) - - coVerify { createPersonEvent(any()) } - } - @Test fun `Returns response when all steps executed`() = runTest { every { stepsBuilder.build(any(), any()) } returns listOf( @@ -171,7 +147,6 @@ internal class OrchestratorViewModelTest { createMockStep(StepId.CONSENT), ) coEvery { mapRefusalOrErrorResult(any(), any()) } returns null - coEvery { shouldCreatePerson(any(), any(), any()) } returns false coEvery { appResponseBuilder(any(), any(), any(), any()) } returns mockk() coJustRun { dailyActivityUseCase(any()) } justRun { addCallbackEvent(any()) } @@ -216,7 +191,6 @@ internal class OrchestratorViewModelTest { createMockStep(StepId.SELECT_SUBJECT_AGE), ) coEvery { mapRefusalOrErrorResult(any(), any()) } returns null - coEvery { shouldCreatePerson(any(), any(), any()) } returns false val captureAndMatchSteps = listOf( createMockStep(StepId.FACE_CAPTURE), createMockStep( @@ -253,7 +227,6 @@ internal class OrchestratorViewModelTest { ), ) coEvery { mapRefusalOrErrorResult(any(), any()) } returns null - coEvery { shouldCreatePerson(any(), any(), any()) } returns false viewModel.handleAction(mockk()) viewModel.handleResult(FaceCaptureResult("", emptyList())) @@ -277,7 +250,6 @@ internal class OrchestratorViewModelTest { ), ) coEvery { mapRefusalOrErrorResult(any(), any()) } returns null - coEvery { shouldCreatePerson(any(), any(), any()) } returns false viewModel.handleAction(mockk()) viewModel.handleResult(FingerprintCaptureResult("", emptyList())) @@ -326,7 +298,6 @@ internal class OrchestratorViewModelTest { ), ) coEvery { mapRefusalOrErrorResult(any(), any()) } returns null - coEvery { shouldCreatePerson(any(), any(), any()) } returns false val format = "SimMatcher" val sample1 = FingerprintCaptureResult.Sample( IFingerIdentifier.LEFT_INDEX_FINGER, diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCaseTest.kt deleted file mode 100644 index dfa01c6e50..0000000000 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/CreatePersonEventUseCaseTest.kt +++ /dev/null @@ -1,190 +0,0 @@ -package com.simprints.feature.orchestrator.usecases - -import com.google.common.truth.Truth.assertThat -import com.simprints.core.domain.fingerprint.IFingerIdentifier -import com.simprints.core.tools.time.TimeHelper -import com.simprints.core.tools.time.Timestamp -import com.simprints.face.capture.FaceCaptureResult -import com.simprints.fingerprint.capture.FingerprintCaptureResult -import com.simprints.infra.events.event.domain.models.PersonCreationEvent -import com.simprints.infra.events.event.domain.models.fingerprint.FingerprintCaptureBiometricsEvent -import com.simprints.infra.events.session.SessionEventRepository -import com.simprints.testtools.common.coroutines.TestCoroutineRule -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.mockk -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Rule -import org.junit.Test - -internal class CreatePersonEventUseCaseTest { - @get:Rule - val testCoroutineRule = TestCoroutineRule() - - @MockK - lateinit var eventRepository: SessionEventRepository - - @MockK - lateinit var timeHelper: TimeHelper - - private lateinit var useCase: CreatePersonEventUseCase - - @Before - fun setUp() { - MockKAnnotations.init(this, relaxed = true) - - every { timeHelper.now() } returns Timestamp(0L) - - coEvery { eventRepository.getCurrentSessionScope() } returns mockk { - every { id } returns "sessionId" - } - - useCase = CreatePersonEventUseCase(eventRepository, timeHelper) - } - - @Test - fun `Does not create event if no biometric data`() = runTest { - coEvery { eventRepository.getEventsInCurrentSession() } returns listOf() - - useCase(listOf()) - - coVerify(exactly = 0) { eventRepository.addOrUpdateEvent(any()) } - } - - @Test - fun `Create event if there is face biometric data`() = runTest { - coEvery { eventRepository.getEventsInCurrentSession() } returns listOf() - - useCase(listOf(FaceCaptureResult("", listOf(createFaceCaptureResultItem())))) - - coVerify { - eventRepository.addOrUpdateEvent( - withArg { - assertThat(it.payload.faceCaptureIds).isEqualTo(listOf(FACE_CAPTURE_ID)) - }, - ) - } - } - - @Test - fun `Create event if there is fingerprint biometric data`() = runTest { - coEvery { eventRepository.getEventsInCurrentSession() } returns listOf( - mockk { - every { payload.id } returns FINGER_CAPTURE_ID - every { payload.fingerprint.template } returns TEMPLATE - }, - ) - - useCase(listOf(FingerprintCaptureResult("", listOf(createFingerprintCaptureResultItem())))) - - coVerify { - eventRepository.addOrUpdateEvent( - withArg { - assertThat(it.payload.fingerprintCaptureIds).isEqualTo(listOf(FINGER_CAPTURE_ID)) - }, - ) - } - } - - @Test - fun `Gets fingerprint from previous PersonCreationEvent (when present) if missing in current callout captures`() = runTest { - coEvery { eventRepository.getEventsInCurrentSession() } returns listOf( - mockk { - every { payload.fingerprintCaptureIds } returns listOf(FINGER_CAPTURE_ID) - every { payload.fingerprintReferenceId } returns FINGER_REFERENCE_ID - }, - ) - - useCase(listOf(FaceCaptureResult("", listOf(createFaceCaptureResultItem())))) - - coVerify { - eventRepository.addOrUpdateEvent( - withArg { - assertThat(it.payload.faceCaptureIds).isEqualTo(listOf(FACE_CAPTURE_ID)) - assertThat(it.payload.fingerprintCaptureIds).isEqualTo(listOf(FINGER_CAPTURE_ID)) - assertThat(it.payload.fingerprintReferenceId).isEqualTo(FINGER_REFERENCE_ID) - }, - ) - } - } - - @Test - fun `Gets face from previous PersonCreationEvent (when present) if missing in current callout captures`() = runTest { - coEvery { eventRepository.getEventsInCurrentSession() } returns listOf( - mockk { - every { payload.faceCaptureIds } returns listOf(FACE_CAPTURE_ID) - every { payload.faceReferenceId } returns FACE_REFERENCE_ID - }, - ) - - useCase(listOf(FingerprintCaptureResult("", listOf(createFingerprintCaptureResultItem())))) - - coVerify { - eventRepository.addOrUpdateEvent( - withArg { - assertThat(it.payload.fingerprintCaptureIds).isEqualTo(listOf(FINGER_CAPTURE_ID)) - assertThat(it.payload.faceCaptureIds).isEqualTo(listOf(FACE_CAPTURE_ID)) - assertThat(it.payload.faceReferenceId).isEqualTo(FACE_REFERENCE_ID) - }, - ) - } - } - - @Test - fun `Uses face from latest PersonCreationEvent (if more than one) if missing in current callout captures`() = runTest { - coEvery { eventRepository.getEventsInCurrentSession() } returns listOf( - mockk { - every { payload.faceCaptureIds } returns listOf(FACE_CAPTURE_ID) - every { payload.faceReferenceId } returns FACE_REFERENCE_ID - every { payload.createdAt } returns Timestamp(2L) - }, - mockk { - every { payload.faceCaptureIds } returns listOf("anotherFaceCaptureId") - every { payload.faceReferenceId } returns "anotherFaceReferenceId" - every { payload.createdAt } returns Timestamp(1L) - }, - ) - - useCase(listOf(FingerprintCaptureResult("", listOf(createFingerprintCaptureResultItem())))) - - coVerify { - eventRepository.addOrUpdateEvent( - withArg { - assertThat(it.payload.fingerprintCaptureIds).isEqualTo(listOf(FINGER_CAPTURE_ID)) - assertThat(it.payload.faceCaptureIds).isEqualTo(listOf(FACE_CAPTURE_ID)) - assertThat(it.payload.faceReferenceId).isEqualTo(FACE_REFERENCE_ID) - }, - ) - } - } - - private fun createFingerprintCaptureResultItem() = FingerprintCaptureResult.Item( - captureEventId = FINGER_CAPTURE_ID, - identifier = IFingerIdentifier.RIGHT_THUMB, - sample = FingerprintCaptureResult.Sample( - IFingerIdentifier.RIGHT_THUMB, - TEMPLATE.toByteArray(), - 0, - null, - "format", - ), - ) - - private fun createFaceCaptureResultItem() = FaceCaptureResult.Item( - captureEventId = FACE_CAPTURE_ID, - index = 0, - sample = FaceCaptureResult.Sample(FACE_CAPTURE_ID, TEMPLATE.toByteArray(), null, "format"), - ) - - companion object { - private const val TEMPLATE = "template" - private const val FINGER_CAPTURE_ID = "fingerprintCaptureId" - private const val FINGER_REFERENCE_ID = "fingerReferenceId" - private const val FACE_CAPTURE_ID = "faceCaptureId" - private const val FACE_REFERENCE_ID = "faceReferenceId" - } -} diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/ShouldCreatePersonUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/ShouldCreatePersonUseCaseTest.kt deleted file mode 100644 index 315d165a7b..0000000000 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/ShouldCreatePersonUseCaseTest.kt +++ /dev/null @@ -1,392 +0,0 @@ -package com.simprints.feature.orchestrator.usecases - -import android.os.Bundle -import com.google.common.truth.Truth.assertThat -import com.simprints.core.domain.tokenization.asTokenizableRaw -import com.simprints.core.tools.time.Timestamp -import com.simprints.face.capture.FaceCaptureResult -import com.simprints.feature.orchestrator.steps.Step -import com.simprints.feature.orchestrator.steps.StepId -import com.simprints.feature.orchestrator.steps.StepStatus -import com.simprints.fingerprint.capture.FingerprintCaptureResult -import com.simprints.infra.config.store.models.GeneralConfiguration -import com.simprints.infra.events.event.domain.models.PersonCreationEvent -import com.simprints.infra.events.session.SessionEventRepository -import com.simprints.infra.orchestration.data.ActionRequest -import com.simprints.infra.orchestration.data.ActionRequestIdentifier -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import java.io.Serializable - -class ShouldCreatePersonUseCaseTest { - @MockK - lateinit var eventRepository: SessionEventRepository - - private lateinit var useCase: ShouldCreatePersonUseCase - - @Before - fun setUp() { - MockKAnnotations.init(this, relaxed = true) - - useCase = ShouldCreatePersonUseCase(eventRepository) - } - - @Test - fun `Returns false if no action`() = runTest { - assertThat( - useCase( - actionRequest = null, - modalities = setOf(GeneralConfiguration.Modality.FINGERPRINT), - results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), - ), - ), - ).isFalse() - } - - @Test - fun `Returns true if action is Enrol`() = runTest { - assertThat( - useCase( - actionRequest = ActionRequest.EnrolActionRequest( - actionIdentifier = ActionRequestIdentifier.fromIntentAction(0L, ""), - projectId = "", - userId = "".asTokenizableRaw(), - moduleId = "".asTokenizableRaw(), - biometricDataSource = "", - metadata = "", - unknownExtras = emptyMap(), - ), - modalities = setOf(GeneralConfiguration.Modality.FINGERPRINT), - results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), - ), - ), - ).isTrue() - } - - @Test - fun `Returns true if action is Identify`() = runTest { - assertThat( - useCase( - actionRequest = ActionRequest.IdentifyActionRequest( - actionIdentifier = ActionRequestIdentifier.fromIntentAction(0L, ""), - projectId = "", - userId = "".asTokenizableRaw(), - moduleId = "".asTokenizableRaw(), - biometricDataSource = "", - metadata = "", - unknownExtras = emptyMap(), - ), - modalities = setOf(GeneralConfiguration.Modality.FINGERPRINT), - results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), - ), - ), - ).isTrue() - } - - @Test - fun `Returns true if actions is Verify`() = runTest { - assertThat( - useCase( - actionRequest = ActionRequest.VerifyActionRequest( - actionIdentifier = ActionRequestIdentifier.fromIntentAction(0L, ""), - projectId = "", - userId = "".asTokenizableRaw(), - moduleId = "".asTokenizableRaw(), - biometricDataSource = "", - metadata = "", - verifyGuid = "", - unknownExtras = emptyMap(), - ), - modalities = setOf(GeneralConfiguration.Modality.FINGERPRINT), - results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), - ), - ), - ).isTrue() - } - - @Test - fun `Returns false if followup action is ConfirmIdentity`() = runTest { - assertThat( - useCase( - actionRequest = ActionRequest.ConfirmIdentityActionRequest( - actionIdentifier = ActionRequestIdentifier.fromIntentAction(0L, ""), - projectId = "", - userId = "".asTokenizableRaw(), - sessionId = "", - selectedGuid = "", - metadata = "", - unknownExtras = emptyMap(), - ), - modalities = setOf(GeneralConfiguration.Modality.FINGERPRINT), - results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), - ), - ), - ).isFalse() - } - - @Test - fun `Returns true if followup action is Enrol last biometric`() = runTest { - assertThat( - useCase( - actionRequest = ActionRequest.EnrolLastBiometricActionRequest( - actionIdentifier = ActionRequestIdentifier.fromIntentAction(0L, ""), - projectId = "", - userId = "".asTokenizableRaw(), - moduleId = "".asTokenizableRaw(), - metadata = "", - sessionId = "", - unknownExtras = emptyMap(), - ), - modalities = setOf(GeneralConfiguration.Modality.FINGERPRINT), - results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), - ), - ), - ).isTrue() - } - - @Test - fun `Returns false if no modalities`() = runTest { - assertThat( - useCase( - actionRequest = flowAction, - modalities = emptySet(), - results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), - ), - ), - ).isFalse() - } - - @Test - fun `Returns false when only fingerprint required and no results`() = runTest { - assertThat( - useCase( - actionRequest = flowAction, - modalities = setOf(GeneralConfiguration.Modality.FINGERPRINT), - results = listOf(createStep(StepId.FINGERPRINT_CAPTURE, null)), - ), - ).isFalse() - } - - @Test - fun `Returns false when only face required and no results`() = runTest { - assertThat( - useCase( - actionRequest = flowAction, - modalities = setOf(GeneralConfiguration.Modality.FACE), - results = listOf(createStep(StepId.FACE_CAPTURE, null)), - ), - ).isFalse() - } - - @Test - fun `Returns false when both modalities required but there are no capture results`() = runTest { - assertThat( - useCase( - actionRequest = flowAction, - modalities = setOf( - GeneralConfiguration.Modality.FACE, - GeneralConfiguration.Modality.FINGERPRINT, - ), - results = emptyList(), - ), - ).isFalse() - } - - @Test - fun `Returns true when only fingerprint required and provided`() = runTest { - assertThat( - useCase( - actionRequest = flowAction, - modalities = setOf(GeneralConfiguration.Modality.FINGERPRINT), - results = listOf( - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), - ), - ), - ).isTrue() - } - - @Test - fun `Returns false when both modalities required and face result missing`() = runTest { - assertThat( - useCase( - actionRequest = flowAction, - modalities = setOf( - GeneralConfiguration.Modality.FACE, - GeneralConfiguration.Modality.FINGERPRINT, - ), - results = listOf( - createStep(StepId.FACE_CAPTURE, null), - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), - ), - ), - ).isFalse() - } - - @Test - fun `Returns false when both modalities required and fingerprint result missing`() = runTest { - assertThat( - useCase( - actionRequest = flowAction, - modalities = setOf( - GeneralConfiguration.Modality.FACE, - GeneralConfiguration.Modality.FINGERPRINT, - ), - results = listOf( - createStep(StepId.FACE_CAPTURE, FaceCaptureResult("", emptyList())), - createStep(StepId.FINGERPRINT_CAPTURE, null), - ), - ), - ).isFalse() - } - - @Test - fun `Returns true when only face required and provided`() = runTest { - assertThat( - useCase( - actionRequest = flowAction, - modalities = setOf(GeneralConfiguration.Modality.FACE), - results = listOf(createStep(StepId.FACE_CAPTURE, FaceCaptureResult("", emptyList()))), - ), - ).isTrue() - } - - @Test - fun `Returns true when both modalities required and both results provided`() = runTest { - assertThat( - useCase( - actionRequest = flowAction, - modalities = setOf( - GeneralConfiguration.Modality.FACE, - GeneralConfiguration.Modality.FINGERPRINT, - ), - results = listOf( - createStep(StepId.FACE_CAPTURE, FaceCaptureResult("", emptyList())), - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), - ), - ), - ).isTrue() - } - - @Test - fun `Returns true when there is a PersonCreationEvent but it's missing a face reference that was scheduled for capture`() = runTest { - val faceCaptureResults = listOf(createStep(StepId.FACE_CAPTURE, FaceCaptureResult("", emptyList()))) - - val sessionEvents = listOf( - PersonCreationEvent( - startTime = Timestamp(0), - fingerprintCaptureIds = listOf("1", "2"), - fingerprintReferenceId = "123", - faceCaptureIds = null, - faceReferenceId = null, - ), - ) - coEvery { eventRepository.getEventsInCurrentSession() } returns sessionEvents - - assertThat( - useCase( - actionRequest = flowAction, - modalities = setOf( - GeneralConfiguration.Modality.FACE, - GeneralConfiguration.Modality.FINGERPRINT, - ), - results = faceCaptureResults, - ), - ).isTrue() - } - - @Test - fun `Returns true when there is a PersonCreationEvent but it's missing a fingerprint reference that was scheduled for capture`() = - runTest { - val fingerprintCaptureResults = listOf(createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList()))) - - val sessionEvents = listOf( - PersonCreationEvent( - startTime = Timestamp(0), - fingerprintCaptureIds = null, - fingerprintReferenceId = null, - faceCaptureIds = listOf("1", "2"), - faceReferenceId = "123", - ), - ) - coEvery { eventRepository.getEventsInCurrentSession() } returns sessionEvents - - assertThat( - useCase( - actionRequest = flowAction, - modalities = setOf( - GeneralConfiguration.Modality.FACE, - GeneralConfiguration.Modality.FINGERPRINT, - ), - results = fingerprintCaptureResults, - ), - ).isTrue() - } - - @Test - fun `Returns false when there are already 2 PersonCreation events in the session`() = runTest { - val sessionEvents = listOf( - PersonCreationEvent( - startTime = Timestamp(0), - fingerprintCaptureIds = null, - fingerprintReferenceId = null, - faceCaptureIds = listOf("1", "2"), - faceReferenceId = "123", - ), - PersonCreationEvent( - startTime = Timestamp(0), - fingerprintCaptureIds = listOf("1", "2"), - fingerprintReferenceId = "123", - faceCaptureIds = listOf("1", "2"), - faceReferenceId = "123", - ), - ) - coEvery { eventRepository.getEventsInCurrentSession() } returns sessionEvents - - assertThat( - useCase( - actionRequest = flowAction, - modalities = setOf( - GeneralConfiguration.Modality.FACE, - GeneralConfiguration.Modality.FINGERPRINT, - ), - results = listOf( - createStep(StepId.FACE_CAPTURE, FaceCaptureResult("", emptyList())), - createStep(StepId.FINGERPRINT_CAPTURE, FingerprintCaptureResult("", emptyList())), - ), - ), - ).isFalse() - } - - private val flowAction = ActionRequest.EnrolActionRequest( - actionIdentifier = ActionRequestIdentifier.fromIntentAction(0L, ""), - projectId = "", - userId = "".asTokenizableRaw(), - moduleId = "".asTokenizableRaw(), - biometricDataSource = "", - metadata = "", - unknownExtras = emptyMap(), - ) - - private fun createStep( - id: Int, - result: Serializable?, - ) = Step( - id = id, - navigationActionId = 1, - destinationId = 1, - payload = Bundle(), - status = StepStatus.COMPLETED, - result = result, - ) -} diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiPersonCreationPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiPersonCreationPayload.kt index 746f61f762..596f775bd0 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiPersonCreationPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiPersonCreationPayload.kt @@ -5,6 +5,7 @@ import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.PersonCreationEvent.PersonCreationPayload @Keep +@Deprecated("Replaced by ApiBiometricReferenceCreationEvent in 2025.1.0") internal data class ApiPersonCreationPayload( override val startTime: ApiTimestamp, val fingerprintCaptureIds: List?, diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/PersonCreationEvent.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/PersonCreationEvent.kt index 40348311d8..054d8652fd 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/PersonCreationEvent.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/PersonCreationEvent.kt @@ -8,6 +8,7 @@ import com.simprints.infra.events.event.domain.models.EventType.PERSON_CREATION import java.util.UUID @Keep +@Deprecated("Replaced by BiometricReferenceCreationEvent in 2025.1.0") data class PersonCreationEvent( override val id: String = UUID.randomUUID().toString(), override val payload: PersonCreationPayload, @@ -40,6 +41,7 @@ data class PersonCreationEvent( // At the end of the sequence of capture, we build a Person object used either for enrolment, verification or identification @Keep + @Deprecated("Replaced by BiometricReferenceCreationEvent") data class PersonCreationPayload( override val createdAt: Timestamp, override val eventVersion: Int, diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/PersonCreationEventValidator.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/PersonCreationEventValidator.kt deleted file mode 100644 index eacf45295b..0000000000 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/PersonCreationEventValidator.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.simprints.infra.events.event.domain.validators - -import com.simprints.infra.events.event.domain.models.Event -import com.simprints.infra.events.event.domain.models.PersonCreationEvent -import com.simprints.infra.events.exceptions.validator.PersonCreationEventException - -internal class PersonCreationEventValidator : EventValidator { - /** - * This validator checks to make sure that no more than the allowed number of PersonCreation events - * are added to the session. - * "Normal" sessions have only one PersonCreation event. - * Sessions that skip a modality (due to Matching Modalities configuration) have two PersonCreation events. - */ - override fun validate( - currentEvents: List, - eventToAdd: Event, - ) { - if (eventToAdd is PersonCreationEvent) { - val existingPersonCreationEventsCount = currentEvents - .filter { it is PersonCreationEvent } - .count { it.id != eventToAdd.id } - if (existingPersonCreationEventsCount > 1) { - throw PersonCreationEventException( - "The session already has the maximum PersonCreationEvents allowed ($existingPersonCreationEventsCount)", - ) - } - } - } -} diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/SessionEventValidatorsFactory.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/SessionEventValidatorsFactory.kt index 67bb34a1be..6d39a76385 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/SessionEventValidatorsFactory.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/SessionEventValidatorsFactory.kt @@ -4,7 +4,6 @@ import javax.inject.Inject internal class SessionEventValidatorsFactory @Inject constructor() { fun build(): Array = arrayOf( - PersonCreationEventValidator(), EnrolmentEventValidator(), ) } From 1e5029f0ee34182a7ac98883830e8310ca9c8969 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 5 Feb 2025 10:45:39 +0200 Subject: [PATCH 10/22] MS-871 Add EnrolmentRecordUpdate model and boilerplate --- .../ApiEnrolmentRecordDeletionPayload.kt | 6 +- .../models/subject/ApiEnrolmentRecordEvent.kt | 8 +++ .../subject/ApiEnrolmentRecordEventPayload.kt | 4 ++ .../subject/ApiEnrolmentRecordMovePayload.kt | 4 -- .../subject/ApiEnrolmentRecordPayloadType.kt | 5 ++ .../ApiEnrolmentRecordUpdatePayload.kt | 19 ++++++ .../sync/down/tasks/EventDownSyncTask.kt | 9 +++ .../event/remote/EventRemoteDataSourceTest.kt | 3 +- .../ApiEnrolmentRecordUpdateEventTest.kt | 56 ++++++++++++++++++ ...nc_7events.json => down_sync_8events.json} | 58 +++++++++++++++++++ .../models/subject/EnrolmentRecordEvent.kt | 1 + .../subject/EnrolmentRecordEventType.kt | 1 + .../subject/EnrolmentRecordUpdateEvent.kt | 30 ++++++++++ 13 files changed, 194 insertions(+), 10 deletions(-) create mode 100644 infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordUpdatePayload.kt create mode 100644 infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordUpdateEventTest.kt rename infra/event-sync/src/test/resources/responses/{down_sync_7events.json => down_sync_8events.json} (79%) create mode 100644 infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordUpdateEvent.kt diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordDeletionPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordDeletionPayload.kt index d0518524fb..3a5c24922a 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordDeletionPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordDeletionPayload.kt @@ -9,11 +9,7 @@ internal data class ApiEnrolmentRecordDeletionPayload( val projectId: String, val moduleId: String, val attendantId: String, -) : ApiEnrolmentRecordEventPayload(ApiEnrolmentRecordPayloadType.EnrolmentRecordDeletion) { - companion object { - const val ENROLMENT_RECORD_DELETION = "EnrolmentRecordDeletion" - } -} +) : ApiEnrolmentRecordEventPayload(ApiEnrolmentRecordPayloadType.EnrolmentRecordDeletion) internal fun ApiEnrolmentRecordDeletionPayload.fromApiToDomain() = EnrolmentRecordDeletionEvent.EnrolmentRecordDeletionPayload( subjectId, diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordEvent.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordEvent.kt index 73970eae05..14a58316ca 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordEvent.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordEvent.kt @@ -5,6 +5,7 @@ import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordCre import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordDeletionEvent import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordEvent import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordMoveEvent +import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordUpdateEvent @Keep internal class ApiEnrolmentRecordEvent( @@ -17,10 +18,17 @@ internal fun ApiEnrolmentRecordEvent.fromApiToDomain(): EnrolmentRecordEvent = w id, (payload as ApiEnrolmentRecordCreationPayload).fromApiToDomain(), ) + ApiEnrolmentRecordPayloadType.EnrolmentRecordDeletion -> EnrolmentRecordDeletionEvent( id, (payload as ApiEnrolmentRecordDeletionPayload).fromApiToDomain(), ) + + ApiEnrolmentRecordPayloadType.EnrolmentRecordUpdate -> EnrolmentRecordUpdateEvent( + id, + (payload as ApiEnrolmentRecordUpdatePayload).fromApiToDomain(), + ) + ApiEnrolmentRecordPayloadType.EnrolmentRecordMove -> EnrolmentRecordMoveEvent( id, (payload as ApiEnrolmentRecordMovePayload).fromApiToDomain(), diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordEventPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordEventPayload.kt index 5f2220e605..1bf235b3f8 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordEventPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordEventPayload.kt @@ -22,6 +22,10 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo value = ApiEnrolmentRecordMovePayload::class, name = ApiEnrolmentRecordPayloadType.ENROLMENT_RECORD_MOVE_KEY, ), + JsonSubTypes.Type( + value = ApiEnrolmentRecordUpdatePayload::class, + name = ApiEnrolmentRecordPayloadType.ENROLMENT_RECORD_UPDATE_KEY, + ), ) internal abstract class ApiEnrolmentRecordEventPayload( val type: ApiEnrolmentRecordPayloadType, diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordMovePayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordMovePayload.kt index 48e0ba28f6..87a8b7d67a 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordMovePayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordMovePayload.kt @@ -32,10 +32,6 @@ internal data class ApiEnrolmentRecordMovePayload( val attendantId: String, val biometricReferences: List?, ) - - companion object { - const val ENROLMENT_RECORD_MOVE = "EnrolmentRecordMove" - } } internal fun ApiEnrolmentRecordMovePayload.fromApiToDomain() = EnrolmentRecordMoveEvent.EnrolmentRecordMovePayload( diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordPayloadType.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordPayloadType.kt index 9c54670936..3df952cc64 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordPayloadType.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordPayloadType.kt @@ -11,6 +11,9 @@ internal enum class ApiEnrolmentRecordPayloadType { // key added: ENROLMENT_RECORD_DELETION_KEY EnrolmentRecordDeletion, + // key added: ENROLMENT_RECORD_UPDATE_KEY + EnrolmentRecordUpdate, + // key added: ENROLMENT_RECORD_MOVE_KEY EnrolmentRecordMove, @@ -20,6 +23,7 @@ internal enum class ApiEnrolmentRecordPayloadType { const val ENROLMENT_RECORD_CREATION_KEY = "EnrolmentRecordCreation" const val ENROLMENT_RECORD_DELETION_KEY = "EnrolmentRecordDeletion" const val ENROLMENT_RECORD_MOVE_KEY = "EnrolmentRecordMove" + const val ENROLMENT_RECORD_UPDATE_KEY = "EnrolmentRecordUpdate" } } @@ -27,4 +31,5 @@ internal fun ApiEnrolmentRecordPayloadType.fromApiToDomain(): EnrolmentRecordEve ApiEnrolmentRecordPayloadType.EnrolmentRecordCreation -> EnrolmentRecordEventType.EnrolmentRecordCreation ApiEnrolmentRecordPayloadType.EnrolmentRecordDeletion -> EnrolmentRecordEventType.EnrolmentRecordDeletion ApiEnrolmentRecordPayloadType.EnrolmentRecordMove -> EnrolmentRecordEventType.EnrolmentRecordMove + ApiEnrolmentRecordPayloadType.EnrolmentRecordUpdate -> EnrolmentRecordEventType.EnrolmentRecordUpdate } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordUpdatePayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordUpdatePayload.kt new file mode 100644 index 0000000000..14ed2e73a6 --- /dev/null +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordUpdatePayload.kt @@ -0,0 +1,19 @@ +package com.simprints.infra.eventsync.event.remote.models.subject + +import androidx.annotation.Keep +import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordUpdateEvent +import com.simprints.infra.eventsync.event.remote.models.subject.biometricref.ApiBiometricReference +import com.simprints.infra.eventsync.event.remote.models.subject.biometricref.fromApiToDomain + +@Keep +internal data class ApiEnrolmentRecordUpdatePayload( + val subjectId: String, + val biometricReferencesAdded: List, + val biometricReferencesRemoved: List, +) : ApiEnrolmentRecordEventPayload(ApiEnrolmentRecordPayloadType.EnrolmentRecordDeletion) + +internal fun ApiEnrolmentRecordUpdatePayload.fromApiToDomain() = EnrolmentRecordUpdateEvent.EnrolmentRecordUpdatePayload( + subjectId, + biometricReferencesAdded.map { it.fromApiToDomain() }, + biometricReferencesRemoved, +) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt index d3adc1b6a6..12d060d187 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt @@ -20,6 +20,7 @@ import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordEve import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordEventType import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordMoveEvent import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordMoveEvent.EnrolmentRecordCreationInMove +import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordUpdateEvent import com.simprints.infra.eventsync.event.remote.EventRemoteDataSource import com.simprints.infra.eventsync.status.down.EventDownSyncScopeRepository import com.simprints.infra.eventsync.status.down.domain.EventDownSyncOperation @@ -168,6 +169,10 @@ internal class EventDownSyncTask @Inject constructor( EnrolmentRecordEventType.EnrolmentRecordMove -> { handleSubjectMoveEvent(operation, event as EnrolmentRecordMoveEvent) } + + EnrolmentRecordEventType.EnrolmentRecordUpdate -> { + handleSubjectUpdateEvent(event as EnrolmentRecordUpdateEvent) + } } }.flatten() @@ -254,6 +259,10 @@ internal class EventDownSyncTask @Inject constructor( private fun handleSubjectDeletionEvent(event: EnrolmentRecordDeletionEvent): List = listOf(Deletion(event.payload.subjectId)) + private fun EventDownSyncTask.handleSubjectUpdateEvent(event: EnrolmentRecordUpdateEvent): List { + TODO("Not yet implemented") + } + companion object { const val EVENTS_BATCH_SIZE = 200 } diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/EventRemoteDataSourceTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/EventRemoteDataSourceTest.kt index 4e9cb3a48b..a54c598655 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/EventRemoteDataSourceTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/EventRemoteDataSourceTest.kt @@ -129,7 +129,7 @@ class EventRemoteDataSourceTest { @Test fun downloadEvents_shouldParseStreamAndEmitBatches() = runTest { val responseStreamWith6Events = - this.javaClass.classLoader?.getResourceAsStream("responses/down_sync_7events.json")!! + this.javaClass.classLoader?.getResourceAsStream("responses/down_sync_8events.json")!! val channel = mockk>(relaxed = true) excludeRecords { channel.isClosedForSend } @@ -148,6 +148,7 @@ class EventRemoteDataSourceTest { EnrolmentRecordEventType.EnrolmentRecordDeletion, EnrolmentRecordEventType.EnrolmentRecordMove, EnrolmentRecordEventType.EnrolmentRecordMove, + EnrolmentRecordEventType.EnrolmentRecordUpdate, ), ) } diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordUpdateEventTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordUpdateEventTest.kt new file mode 100644 index 0000000000..aeb54ed7d2 --- /dev/null +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordUpdateEventTest.kt @@ -0,0 +1,56 @@ +package com.simprints.infra.eventsync.event.remote.models.subject + +import com.google.common.truth.Truth.assertThat +import com.simprints.core.domain.fingerprint.IFingerIdentifier +import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordUpdateEvent +import com.simprints.infra.events.event.domain.models.subject.FaceReference +import com.simprints.infra.events.event.domain.models.subject.FaceTemplate +import com.simprints.infra.events.event.domain.models.subject.FingerprintReference +import com.simprints.infra.events.event.domain.models.subject.FingerprintTemplate +import com.simprints.infra.eventsync.event.remote.models.subject.biometricref.face.ApiFaceReference +import com.simprints.infra.eventsync.event.remote.models.subject.biometricref.face.ApiFaceTemplate +import com.simprints.infra.eventsync.event.remote.models.subject.biometricref.fingerprint.ApiFingerprintReference +import com.simprints.infra.eventsync.event.remote.models.subject.biometricref.fingerprint.ApiFingerprintTemplate +import org.junit.Test + +class ApiEnrolmentRecordUpdateEventTest { + @Test + fun convert_EnrolmentRecordUpdateEvent() { + val apiPayload = ApiEnrolmentRecordUpdatePayload( + "subjectId", + listOf( + ApiFingerprintReference( + "fpRefId", + listOf( + ApiFingerprintTemplate(10, "template", IFingerIdentifier.LEFT_THUMB), + ), + "NEC_1", + ), + ApiFaceReference( + "fRefId", + listOf(ApiFaceTemplate("template")), + "ROC_3", + ), + ), + listOf("fpRefId2"), + ) + val expectedPayload = EnrolmentRecordUpdateEvent.EnrolmentRecordUpdatePayload( + "subjectId", + listOf( + FingerprintReference( + "fpRefId", + listOf(FingerprintTemplate(10, "template", IFingerIdentifier.LEFT_THUMB)), + "NEC_1", + ), + FaceReference( + "fRefId", + listOf(FaceTemplate("template")), + "ROC_3", + ), + ), + listOf("fpRefId2"), + ) + + assertThat(apiPayload.fromApiToDomain()).isEqualTo(expectedPayload) + } +} diff --git a/infra/event-sync/src/test/resources/responses/down_sync_7events.json b/infra/event-sync/src/test/resources/responses/down_sync_8events.json similarity index 79% rename from infra/event-sync/src/test/resources/responses/down_sync_7events.json rename to infra/event-sync/src/test/resources/responses/down_sync_8events.json index 7a78be498c..0d98025ab3 100644 --- a/infra/event-sync/src/test/resources/responses/down_sync_7events.json +++ b/infra/event-sync/src/test/resources/responses/down_sync_8events.json @@ -357,5 +357,63 @@ "type": "EnrolmentRecordMove", "version": 0 } + }, + { + "id": "a3102202-956b-4444-bf3e-75c39cee35ba", + "labels": { + "attendantId": [ + "user a" + ], + "subjectId": [ + "5d08d867-fa87-41e4-8df0-98c7256950c7" + ] + }, + "payload": { + "subjectId": "5d08d867-fa87-41e4-8df0-98c7256950c7", + "biometricReferencesAdded": [ + { + "format": "NEC_1", + "templates": [ + { + "finger": "LEFT_INDEX_FINGER", + "quality": 55, + "template": "nN33bhY4llJZZFI+3NEsHSDWSDjiNBJCKlc+Pi0HEL/G0TVo3oEHxu+7tOxwze454ExIJnhijFPZxn6pEXjKEJzoaFrN0ncQwsuA7ncQ4BZVfxf7V6YqO6ornrfrv1z0ViYZrItT2yJ5fIdV1NFXb+T2MHxVVQdEs8VZLFI68+QAxLS/gvFKttQ0kjs4UQfGcYSq6tH2vHidTwFtg9F7SZAfzVioC++l7c/7AneN91HBZVCf8q1vChKMXLqaDe7MRIe6nuaRZLGAy/jz5kloBabPFarevp1b0DoFGDTUTzlGDVIwYLPUYqH2myrM2ykbA339GTFZzmYx2+8SQX7px5akcZ35p0bGiNJGAG1odUuqu4DMBMSn4mN3QAzV5BNPNAhPfakbJd+1oeSQOfkKSU0Uh6+qTwW449ZZnjkYRCJn7RI94mFv" + }, + { + "finger": "LEFT_THUMB", + "quality": 56, + "template": "uoHuxFvqWsnNhequ0JkpOoic6D3qLaFhXvL5R5i7xyWpJALAcLBP0LzQbtrvQTYfuhm3pdJa6zkCLH1jGOshLKJsTFqBZk+MPb9+ol0ugGsfSh7UpkLemr3lxgWRCNSzsktzg7cvZvkdv5GIDfi5aObd/MJ5AeNrNXOI6wpuobiQhIeVciBNYsO/MaaNBw8y+Tiqq8eQ1LAz1vo/tuvIWhN45KWEX4BXXePYnPB3J9VNNFsDUDE7Ex6hm5v42dWK53g/yWa1XTDDZHA2yTLWJGrsfDybaVbIPiCJ3NP1ZCAD+iBPdR8B1GzFfkKI31IqZPKEqCvTQUirZDV7pOo6OjozAGAlcscgozA+r2oLqb3agCuimDUpPIP1zwPs+AFIusiwI1wmGEh2zg2Yo7G+wPy3flnrjtJ1tZP08op2k5U0kzJCb2l8KA+G" + }, + { + "finger": "RIGHT_4TH_FINGER", + "quality": 50, + "template": "gnoXCrttqcGy21pE8lHHIvHLZasSfUpWW8/ckM9Sq+DOLOdsueG4SIUdDr9kANtC8Fx+thOPKBO2YMQdfgHplrqlgPql6SB0iGZ7MGV8L8ly3er7QvNyCeNd/NjDPVrJoEAF71XYX7P7WiQLGunkLEsnoD+87H9Id/y4cr6eWTaj7wfXIG/j4ZXIUHn285BfxxJVE76AGc5owXZYX5xcbrWv0CogXj9jEaxh2zhZ/ETX/8ux6HN3K3JatQV8yS6AEB/qCfSPnZjYg1+1tox3iqaguS0zczYyg6Hc7QBpwEgLUhOKAe7adZwDj4fQKX1NHdVcl3WJH/X8MMkP+XzhiD4id00uppoZAN36n1mmXBjY7kdFkBuZn35oPOtFluWdDO8+4x2A8kc5wUhT4xOgkaFNSYuwskqRgHgJQfePImwDE6vzqhuoT/s=" + }, + { + "finger": "RIGHT_5TH_FINGER", + "quality": 44, + "template": "bNzfaVB8BKjhcIrY8Q3TSDG0cRXY3sbMUwRW0b4tc0e7xvv9gkkgMJrzeYHWcc7uGoDG0eI61Q+O9OVsmwdJudPembgkyK3loQRhsjQM4UIpGQu8NkGEbAn+9gwmYtTi1ezkzLbjknq5ktHDUxaWPV0PDhNo76cki5HSQRT7Ex1cbA2oJ5mn8sNeQzIoHEDo3VjJfYLgvWZVuugBcFaCLZw2oHUjfhounWZ900jZUzGP3aw0kv52fjkKdmpkldtbycQwOtzbnFnGXXSxR9MfHeBu64MuvoX3LGhMALtQpp/O+3noRxldRmcdu7mTEzuynmZD4v0lQV4X/tstVjplQ1hBug5zGo2W7ObXwcplpRsj0+/dbtmUf9Cck089DkrLd6PWEQOg0vId0WunisR7YiYUj3GKTfBP76JtrPfCJnsJRQy2+9mcJgM=" + } + ], + "type": "FingerprintReference" + }, + { + "format": "RANK_ONE_1_23", + "templates": [ + { + "template": "xq98xkmiTbqHdoYcjTP0x7nb/iX3vl1ltNev/w+ZCvzksLSsylKx2DmPMKVeuKnsgGjHElgI7YrpJAxIFR7PqmvwNnwr/y4PCr2G1/sfetE9Jv8DAzL0mcb2D02aQzZ1hjiAUEeTnE0aUiWtlSMj/ijDvZfRcwTd4xxfyHAE0GEnRB+yvuCimOeQ3lL0bZkjR6okrt48xEWBkgsPHNV7UujGjNPBiOSFHzwcJaidBHPPVkAuBhIXHdOo4jVedYB+2YBQqfrqeCLgL1jMvUKYvvGXszc3eQUL+vQuBl+pqe1j8Yr0l74cKqABfPrkCcb50Vi1DQUiNWTXvkmY3+/T7w8hFp3gWoFm70A/f8kYiBaVVd0Y4Kb5/Mu1UE6kg6ScG8O+z9GtYkpzXvpqRLoyQj/AJtNltZQ70hx1nccl1lUx6ftchbfuJX8qng==" + }, + { + "template": "xlzqoNui8yoJduJ48HxnyLzt2wR/Ey1m04Gq8+v4GXlJwQgQNPQLLUkZINR9b7+91yLb8CntoVi0DF3It4wNpQOptL2rynVPL5J5NDrJJsOdMEegbOVup4Co4TbbykalKAcQo/BN1TSpjrh3Ho1mstSsg90bpvY7boI0b7Uql5H6W1GUri2NqnLMw/GOIZiHmmXs4fnWXK9t4ah0i1ctrIbseW8VIViiNe9NzE202HJXJX51+nlLdxXbm9m5bQZhGKQxO4YJdmldqCwIQmBW4D6dw45NbJnsxhN+jpIQV5kpYgQXNRqrzeqo54S4Ta9RulZfJIsFpgK6F9eSBqVjn9iyIvDUXfjes6kcsU8dbWq91apxtREacoJh3b0jlcM6jxpJEaGUSVoVTzz68AqvAB7e11rypconzCPg/sg2fs/Pef85ZvvXfqnR" + } + ], + "type": "FaceReference" + } + ], + "biometricReferencesRemoved": [ + "5d08d867-fa87-41e4-8df0-98c7256950c7" + ], + "type": "EnrolmentRecordUpdate" + } } ] diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordEvent.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordEvent.kt index bbe87336eb..63c62d3c64 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordEvent.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordEvent.kt @@ -13,6 +13,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo JsonSubTypes.Type(value = EnrolmentRecordCreationEvent::class, name = "EnrolmentRecordCreation"), JsonSubTypes.Type(value = EnrolmentRecordMoveEvent::class, name = "EnrolmentRecordMove"), JsonSubTypes.Type(value = EnrolmentRecordDeletionEvent::class, name = "EnrolmentRecordDeletion"), + JsonSubTypes.Type(value = EnrolmentRecordUpdateEvent::class, name = "EnrolmentRecordUpdate"), ) @Keep sealed class EnrolmentRecordEvent( diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordEventType.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordEventType.kt index a6e8815508..17448d96a2 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordEventType.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordEventType.kt @@ -7,4 +7,5 @@ enum class EnrolmentRecordEventType { EnrolmentRecordCreation, EnrolmentRecordDeletion, EnrolmentRecordMove, + EnrolmentRecordUpdate, } diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordUpdateEvent.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordUpdateEvent.kt new file mode 100644 index 0000000000..4ca63c97f8 --- /dev/null +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordUpdateEvent.kt @@ -0,0 +1,30 @@ +package com.simprints.infra.events.event.domain.models.subject + +import androidx.annotation.Keep +import java.util.UUID + +@Keep +data class EnrolmentRecordUpdateEvent( + override val id: String, + val payload: EnrolmentRecordUpdatePayload, +) : EnrolmentRecordEvent(id, EnrolmentRecordEventType.EnrolmentRecordUpdate) { + constructor( + subjectId: String, + biometricReferencesAdded: List, + biometricReferencesRemoved: List, + ) : this( + UUID.randomUUID().toString(), + EnrolmentRecordUpdatePayload( + subjectId, + biometricReferencesAdded, + biometricReferencesRemoved, + ), + ) + + @Keep + data class EnrolmentRecordUpdatePayload( + val subjectId: String, + val biometricReferencesAdded: List, + val biometricReferencesRemoved: List, + ) +} From e6ee1579e2e41c2031253808cf43238134a59265 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 10 Feb 2025 16:05:27 +0200 Subject: [PATCH 11/22] MS-871 Handle record update down-sync event --- .../sync/down/tasks/EventDownSyncTask.kt | 17 +++- .../sync/down/tasks/SubjectFactory.kt | 19 ++++ .../sync/down/tasks/EventDownSyncTaskTest.kt | 62 ++++++++++++ .../sync/down/tasks/SubjectFactoryTest.kt | 99 +++++++++++++++++++ 4 files changed, 193 insertions(+), 4 deletions(-) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt index 12d060d187..755faabcc4 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt @@ -11,6 +11,7 @@ import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepositor import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Creation import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Deletion +import com.simprints.infra.enrolment.records.repository.domain.models.SubjectQuery import com.simprints.infra.events.EventRepository import com.simprints.infra.events.event.domain.models.downsync.EventDownSyncRequestEvent import com.simprints.infra.events.event.domain.models.scope.EventScope @@ -52,7 +53,7 @@ internal class EventDownSyncTask @Inject constructor( scope: CoroutineScope, operation: EventDownSyncOperation, eventScope: EventScope, - project: Project + project: Project, ): Flow = flow { var lastOperation = operation.copy() var count = 0 @@ -153,7 +154,7 @@ internal class EventDownSyncTask @Inject constructor( operation: EventDownSyncOperation, batchOfEventsToProcess: MutableList, lastOperation: EventDownSyncOperation, - project: Project + project: Project, ): EventDownSyncOperation { val actions = batchOfEventsToProcess .map { event -> @@ -259,8 +260,16 @@ internal class EventDownSyncTask @Inject constructor( private fun handleSubjectDeletionEvent(event: EnrolmentRecordDeletionEvent): List = listOf(Deletion(event.payload.subjectId)) - private fun EventDownSyncTask.handleSubjectUpdateEvent(event: EnrolmentRecordUpdateEvent): List { - TODO("Not yet implemented") + private suspend fun handleSubjectUpdateEvent(event: EnrolmentRecordUpdateEvent): List { + val existingSubject = enrolmentRecordRepository.load(SubjectQuery(subjectId = event.payload.subjectId)).first() + val subject = subjectFactory.buildSubjectFromUpdatePayload(existingSubject, event.payload) + + return if (subject.fingerprintSamples.isNotEmpty() || subject.faceSamples.isNotEmpty()) { + listOf(Creation(subject)) + } else { + // Having no samples after update is equal to deletion + listOf(Deletion(event.payload.subjectId)) + } } companion object { diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt index c8f9f76008..8f6d53d99b 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt @@ -11,6 +11,7 @@ import com.simprints.infra.enrolment.records.repository.domain.models.Subject import com.simprints.infra.events.event.domain.models.subject.BiometricReference import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordCreationEvent.EnrolmentRecordCreationPayload import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordMoveEvent.EnrolmentRecordCreationInMove +import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordUpdateEvent.EnrolmentRecordUpdatePayload import com.simprints.infra.events.event.domain.models.subject.FaceReference import com.simprints.infra.events.event.domain.models.subject.FaceTemplate import com.simprints.infra.events.event.domain.models.subject.FingerprintReference @@ -45,6 +46,24 @@ class SubjectFactory @Inject constructor( ) } + fun buildSubjectFromUpdatePayload( + existingSubject: Subject, + payload: EnrolmentRecordUpdatePayload, + ): Subject { + val removedBiometricReferences = payload.biometricReferencesRemoved.toSet() // to make lookup O(1) + val addedFaceSamples = extractFaceSamplesFromBiometricReferences(payload.biometricReferencesAdded) + val addedFingerprintSamples = extractFingerprintSamplesFromBiometricReferences(payload.biometricReferencesAdded) + + return existingSubject.copy( + faceSamples = existingSubject.faceSamples + .filterNot { it.referenceId in removedBiometricReferences } + .plus(addedFaceSamples), + fingerprintSamples = existingSubject.fingerprintSamples + .filterNot { it.referenceId in removedBiometricReferences } + .plus(addedFingerprintSamples), + ) + } + fun buildSubjectFromCaptureResults( projectId: String, attendantId: TokenizableString, diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt index 5eae2b5f64..53710559fe 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt @@ -1,6 +1,7 @@ package com.simprints.infra.eventsync.sync.down.tasks import com.google.common.truth.Truth.assertThat +import com.simprints.core.domain.face.FaceSample import com.simprints.core.domain.tokenization.asTokenizableRaw import com.simprints.core.tools.time.TimeHelper import com.simprints.infra.authstore.exceptions.RemoteDbNotSignedInException @@ -8,6 +9,7 @@ import com.simprints.infra.config.store.models.DeviceConfiguration import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.sync.ConfigManager import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository +import com.simprints.infra.enrolment.records.repository.domain.models.Subject import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Creation import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Deletion import com.simprints.infra.events.EventRepository @@ -17,6 +19,7 @@ import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordCre import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordDeletionEvent import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordEvent import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordMoveEvent +import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordUpdateEvent import com.simprints.infra.events.event.domain.models.subject.FaceReference import com.simprints.infra.events.event.domain.models.subject.FaceTemplate import com.simprints.infra.events.sampledata.SampleDefaults.DEFAULT_MODULE_ID @@ -106,6 +109,11 @@ class EventDownSyncTaskTest { DEFAULT_USER_ID, ), ) + val ENROLMENT_RECORD_UPDATE = EnrolmentRecordUpdateEvent( + "subjectId", + listOf(FaceReference("id", listOf(FaceTemplate("template")), "format")), + listOf("referenceIdToDelete"), + ) } private val projectOp = SampleSyncScopes.projectDownSyncScope.operations.first() @@ -502,6 +510,60 @@ class EventDownSyncTaskTest { } } + @Test + fun downSync_shouldProcessRecordUpdateEvent_withCreations() = runTest { + coEvery { enrolmentRecordRepository.load(any()) } returns listOf( + Subject( + subjectId = "subjectId", + projectId = "projectId", + attendantId = "moduleId".asTokenizableRaw(), + moduleId = "attendantId".asTokenizableRaw(), + faceSamples = listOf( + FaceSample(byteArrayOf(), "format", "referenceId"), + ), + ), + ) + + val event = ENROLMENT_RECORD_UPDATE + mockProgressEmission(listOf(event)) + + eventDownSyncTask.downSync(this, projectOp, eventScope, project).toList() + + coVerify { + enrolmentRecordRepository.performActions( + withArg { actions -> actions.all { it is Creation } }, + project, + ) + } + } + + @Test + fun downSync_shouldProcessRecordUpdateEvent_withDeletions() = runTest { + coEvery { enrolmentRecordRepository.load(any()) } returns listOf( + Subject( + subjectId = "subjectId", + projectId = "projectId", + attendantId = "moduleId".asTokenizableRaw(), + moduleId = "attendantId".asTokenizableRaw(), + faceSamples = listOf( + FaceSample(byteArrayOf(), "format", "referenceIdToDelete"), + ), + ), + ) + + val event = ENROLMENT_RECORD_UPDATE + mockProgressEmission(listOf(event)) + + eventDownSyncTask.downSync(this, projectOp, eventScope, project).toList() + + coVerify { + enrolmentRecordRepository.performActions( + withArg { actions -> actions.all { it is Deletion } }, + project, + ) + } + } + private suspend fun mockProgressEmission(progressEvents: List) { downloadEventsChannel = Channel(capacity = Channel.UNLIMITED) coEvery { eventRemoteDataSource.getEvents(any(), any(), any()) } returns EventDownSyncResult( diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactoryTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactoryTest.kt index a957f3add1..b0deeeb2fd 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactoryTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactoryTest.kt @@ -12,6 +12,7 @@ import com.simprints.fingerprint.capture.FingerprintCaptureResult import com.simprints.infra.enrolment.records.repository.domain.models.Subject import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordCreationEvent import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordMoveEvent +import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordUpdateEvent.EnrolmentRecordUpdatePayload import com.simprints.infra.events.event.domain.models.subject.FaceReference import com.simprints.infra.events.event.domain.models.subject.FaceTemplate import com.simprints.infra.events.event.domain.models.subject.FingerprintReference @@ -123,6 +124,104 @@ class SubjectFactoryTest { assertThat(result).isEqualTo(expected) } + @Test + fun `when buildSubjectFromUpdatePayload is called, correct samples list is created`() { + val subject = Subject( + subjectId = SUBJECT_ID, + projectId = PROJECT_ID, + attendantId = ATTENDANT_ID, + moduleId = MODULE_ID, + fingerprintSamples = listOf( + FingerprintSample( + fingerIdentifier = IDENTIFIER, + template = BASE_64_BYTES, + templateQualityScore = QUALITY, + format = REFERENCE_FORMAT, + referenceId = "referenceId-finger-1", + ), + FingerprintSample( + fingerIdentifier = IDENTIFIER, + template = BASE_64_BYTES, + templateQualityScore = QUALITY, + format = REFERENCE_FORMAT, + referenceId = "referenceId-finger-2", + ), + ), + faceSamples = listOf( + FaceSample( + template = BASE_64_BYTES, + format = REFERENCE_FORMAT, + referenceId = "referenceId-finger-3", + ), + FaceSample( + template = BASE_64_BYTES, + format = REFERENCE_FORMAT, + referenceId = "referenceId-finger-4", + ), + ), + ) + + val payload = EnrolmentRecordUpdatePayload( + subjectId = SUBJECT_ID, + biometricReferencesRemoved = listOf("referenceId-finger-3", "referenceId-finger-2"), + biometricReferencesAdded = listOf( + FingerprintReference( + id = "referenceId-finger-5", + format = REFERENCE_FORMAT, + templates = listOf( + FingerprintTemplate( + quality = QUALITY, + template = BASE_64_BYTES.toString(), + finger = IFingerIdentifier.LEFT_THUMB, + ), + ), + ), + FaceReference( + id = "referenceId-finger-6", + format = REFERENCE_FORMAT, + templates = listOf(FaceTemplate(template = BASE_64_BYTES.toString())), + ), + ), + ) + val result = factory.buildSubjectFromUpdatePayload(subject, payload) + + val expected = Subject( + subjectId = SUBJECT_ID, + projectId = PROJECT_ID, + attendantId = ATTENDANT_ID, + moduleId = MODULE_ID, + fingerprintSamples = listOf( + FingerprintSample( + fingerIdentifier = IDENTIFIER, + template = BASE_64_BYTES, + templateQualityScore = QUALITY, + format = REFERENCE_FORMAT, + referenceId = "referenceId-finger-1", + ), + FingerprintSample( + fingerIdentifier = IDENTIFIER, + template = BASE_64_BYTES, + templateQualityScore = QUALITY, + format = REFERENCE_FORMAT, + referenceId = "referenceId-finger-5", + ), + ), + faceSamples = listOf( + FaceSample( + template = BASE_64_BYTES, + format = REFERENCE_FORMAT, + referenceId = "referenceId-finger-4", + ), + FaceSample( + template = BASE_64_BYTES, + format = REFERENCE_FORMAT, + referenceId = "referenceId-finger-6", + ), + ), + ) + assertThat(result).isEqualTo(expected) + } + @Test fun `when buildSubjectFromCaptureResults is called, correct subject is built`() { every { UUID.randomUUID().toString() } returns SUBJECT_ID From 4a3580a5b4238c3105e611cd3be53f314b7faedd Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 10 Feb 2025 16:33:48 +0200 Subject: [PATCH 12/22] MS-871 Increase sync state database version to force destructive migration --- .../4.json | 84 +++++++++++++++++++ .../status/EventSyncStatusDatabase.kt | 9 +- .../down/domain/EventDownSyncOperation.kt | 21 +---- .../down/domain/EventDownSyncOperationTest.kt | 18 +--- 4 files changed, 100 insertions(+), 32 deletions(-) create mode 100644 infra/event-sync/schemas/com.simprints.infra.eventsync.status.EventSyncStatusDatabase/4.json diff --git a/infra/event-sync/schemas/com.simprints.infra.eventsync.status.EventSyncStatusDatabase/4.json b/infra/event-sync/schemas/com.simprints.infra.eventsync.status.EventSyncStatusDatabase/4.json new file mode 100644 index 0000000000..55cb911755 --- /dev/null +++ b/infra/event-sync/schemas/com.simprints.infra.eventsync.status.EventSyncStatusDatabase/4.json @@ -0,0 +1,84 @@ +{ + "formatVersion": 1, + "database": { + "version": 4, + "identityHash": "fb9ad2ed04bc63d696f394aa7b2351c2", + "entities": [ + { + "tableName": "DbEventsDownSyncOperation", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `lastState` TEXT, `lastEventId` TEXT, `lastUpdatedTime` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastState", + "columnName": "lastState", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastEventId", + "columnName": "lastEventId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastUpdatedTime", + "columnName": "lastUpdatedTime", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "DbEventsUpSyncOperation", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `lastState` TEXT, `lastUpdatedTime` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastState", + "columnName": "lastState", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastUpdatedTime", + "columnName": "lastUpdatedTime", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fb9ad2ed04bc63d696f394aa7b2351c2')" + ] + } +} \ No newline at end of file diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/EventSyncStatusDatabase.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/EventSyncStatusDatabase.kt index e4153d3c14..96ae9c2f2a 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/EventSyncStatusDatabase.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/EventSyncStatusDatabase.kt @@ -11,7 +11,14 @@ import com.simprints.infra.eventsync.status.down.local.DbEventsDownSyncOperation import com.simprints.infra.eventsync.status.up.local.DbEventUpSyncOperationStateDao import com.simprints.infra.eventsync.status.up.local.DbEventsUpSyncOperationState -@Database(entities = [DbEventsDownSyncOperationState::class, DbEventsUpSyncOperationState::class], version = 3, exportSchema = true) +@Database( + entities = [ + DbEventsDownSyncOperationState::class, + DbEventsUpSyncOperationState::class, + ], + version = 4, + exportSchema = true, +) @TypeConverters(Converters::class) @Keep internal abstract class EventSyncStatusDatabase : RoomDatabase() { diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/down/domain/EventDownSyncOperation.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/down/domain/EventDownSyncOperation.kt index a407a1ea7c..550517b43f 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/down/domain/EventDownSyncOperation.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/status/down/domain/EventDownSyncOperation.kt @@ -19,22 +19,9 @@ internal data class EventDownSyncOperation( // Unique key: all request params expect for lastEventId internal fun getUniqueKey(): String = with(this.queryEvent) { - UUID - .nameUUIDFromBytes( - ( - projectId + - (attendantId ?: "") + - (subjectId ?: "") + - (moduleId ?: "") + - modes.joinToString { it.name } + - oldTypes - ).toByteArray(), - ).toString() - } - - companion object { - // We need to keep this old types otherwise the unique key of the down-sync will change - // and we will need to down-sync again from scratch. - internal var oldTypes = "ENROLMENT_RECORD_CREATION, ENROLMENT_RECORD_MOVE, ENROLMENT_RECORD_DELETION" + listOfNotNull(projectId, attendantId, subjectId, moduleId) + .joinToString(separator = "") + .toByteArray() + .let { UUID.nameUUIDFromBytes(it).toString() } } } diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/status/down/domain/EventDownSyncOperationTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/status/down/domain/EventDownSyncOperationTest.kt index 15efc6e9ef..d52de8cde1 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/status/down/domain/EventDownSyncOperationTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/status/down/domain/EventDownSyncOperationTest.kt @@ -1,7 +1,6 @@ package com.simprints.infra.eventsync.status.down.domain import com.google.common.truth.Truth.assertThat -import com.simprints.infra.events.sampledata.SampleDefaults.DEFAULT_MODES import com.simprints.infra.events.sampledata.SampleDefaults.DEFAULT_MODULE_ID import com.simprints.infra.events.sampledata.SampleDefaults.DEFAULT_MODULE_ID_2 import com.simprints.infra.events.sampledata.SampleDefaults.DEFAULT_PROJECT_ID @@ -9,7 +8,6 @@ import com.simprints.infra.events.sampledata.SampleDefaults.DEFAULT_USER_ID import com.simprints.infra.eventsync.SampleSyncScopes.modulesDownSyncScope import com.simprints.infra.eventsync.SampleSyncScopes.projectDownSyncScope import com.simprints.infra.eventsync.SampleSyncScopes.userDownSyncScope -import com.simprints.infra.eventsync.status.down.domain.EventDownSyncOperation.Companion.oldTypes import org.junit.Test import java.util.UUID @@ -18,9 +16,7 @@ class EventDownSyncOperationTest { fun eventDownSyncOperationForProjectScope_hasAnUniqueKey() { val op = projectDownSyncScope.operations.first() assertThat(op.getUniqueKey()).isEqualTo( - uuidFrom( - "${DEFAULT_PROJECT_ID}${DEFAULT_MODES.joinToString { it.name }}$oldTypes", - ), + uuidFrom(DEFAULT_PROJECT_ID), ) } @@ -28,9 +24,7 @@ class EventDownSyncOperationTest { fun eventDownSyncOperationForUserScope_hasAnUniqueKey() { val op = userDownSyncScope.operations.first() assertThat(op.getUniqueKey()).isEqualTo( - uuidFrom( - "$DEFAULT_PROJECT_ID${DEFAULT_USER_ID.value}${DEFAULT_MODES.joinToString { it.name }}$oldTypes", - ), + uuidFrom("$DEFAULT_PROJECT_ID${DEFAULT_USER_ID.value}"), ) } @@ -40,15 +34,11 @@ class EventDownSyncOperationTest { val op1 = modulesDownSyncScope.operations[1] assertThat(op.getUniqueKey()).isEqualTo( - uuidFrom( - "$DEFAULT_PROJECT_ID${DEFAULT_MODULE_ID.value}${DEFAULT_MODES.joinToString { it.name }}$oldTypes", - ), + uuidFrom("$DEFAULT_PROJECT_ID${DEFAULT_MODULE_ID.value}"), ) assertThat(op1.getUniqueKey()).isEqualTo( - uuidFrom( - "$DEFAULT_PROJECT_ID${DEFAULT_MODULE_ID_2.value}${DEFAULT_MODES.joinToString { it.name }}$oldTypes", - ), + uuidFrom("$DEFAULT_PROJECT_ID${DEFAULT_MODULE_ID_2.value}"), ) } From 99aca605d2f0ddc84bb8833214403feb80f145d1 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 10 Feb 2025 17:29:28 +0200 Subject: [PATCH 13/22] MS-871 Update SID version label --- build-logic/build_properties.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build-logic/build_properties.gradle.kts b/build-logic/build_properties.gradle.kts index 50da31676a..56e6ad7b42 100644 --- a/build-logic/build_properties.gradle.kts +++ b/build-logic/build_properties.gradle.kts @@ -16,8 +16,9 @@ extra.apply { * Dev version >= 2024.2.1 is required for receiving biometric sdk age restrictions * Dev version >= 2024.2.2 is required for float quality thresholds * Dev version >= 2024.3.0 is required to receive configuration ID + * Dev version >= 2025.2.0 is required to support enrolment record updates */ - set("VERSION_NAME", "2025.1.0") + set("VERSION_NAME", "2025.2.0") /** * Build type. The version code describes which build type was used for the build. From b5170520424dc2368a13bfc016038d06105e7a6f Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Tue, 11 Feb 2025 10:58:50 +0200 Subject: [PATCH 14/22] MS-871 Fix double schedule of sync worker after successful login --- .../usecases/StartBackgroundSyncUseCase.kt | 9 ++++----- .../usecases/StartBackgroundSyncUseCaseTest.kt | 9 +++------ .../com/simprints/infra/sync/SyncOrchestrator.kt | 4 ++-- .../simprints/infra/sync/SyncOrchestratorImpl.kt | 7 ++++--- .../infra/sync/SyncOrchestratorImplTest.kt | 15 +++++++++++++++ 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCase.kt b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCase.kt index 8369ea556d..0e6a13503d 100644 --- a/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCase.kt +++ b/feature/login-check/src/main/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCase.kt @@ -10,11 +10,10 @@ internal class StartBackgroundSyncUseCase @Inject constructor( private val configManager: ConfigManager, ) { suspend operator fun invoke() { - syncOrchestrator.scheduleBackgroundWork() - val frequency = configManager.getProjectConfiguration().synchronization.frequency - if (frequency == SynchronizationConfiguration.Frequency.PERIODICALLY_AND_ON_SESSION_START) { - syncOrchestrator.startEventSync() - } + + syncOrchestrator.scheduleBackgroundWork( + withDelay = frequency != SynchronizationConfiguration.Frequency.PERIODICALLY_AND_ON_SESSION_START, + ) } } diff --git a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt index 41bca6b3b9..b23f046c8c 100644 --- a/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt +++ b/feature/login-check/src/test/java/com/simprints/feature/logincheck/usecases/StartBackgroundSyncUseCaseTest.kt @@ -7,7 +7,6 @@ import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.MockK -import io.mockk.verify import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -38,9 +37,7 @@ class StartBackgroundSyncUseCaseTest { useCase.invoke() - coVerify { - syncOrchestrator.scheduleBackgroundWork() - } + coVerify { syncOrchestrator.scheduleBackgroundWork(any()) } } @Test @@ -50,7 +47,7 @@ class StartBackgroundSyncUseCaseTest { useCase.invoke() - verify { syncOrchestrator.startEventSync() } + coVerify { syncOrchestrator.scheduleBackgroundWork(eq(false)) } } @Test @@ -60,6 +57,6 @@ class StartBackgroundSyncUseCaseTest { useCase.invoke() - verify(exactly = 0) { syncOrchestrator.startEventSync() } + coVerify { syncOrchestrator.scheduleBackgroundWork(eq(true)) } } } diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt index d88ea8e8a5..57d67bff67 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestrator.kt @@ -3,7 +3,7 @@ package com.simprints.infra.sync import kotlinx.coroutines.flow.Flow interface SyncOrchestrator { - suspend fun scheduleBackgroundWork() + suspend fun scheduleBackgroundWork(withDelay: Boolean = false) suspend fun cancelBackgroundWork() @@ -13,7 +13,7 @@ interface SyncOrchestrator { */ fun refreshConfiguration(): Flow - fun rescheduleEventSync() + fun rescheduleEventSync(withDelay: Boolean = false) fun cancelEventSync() diff --git a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt index c9b73acabe..ba22289a12 100644 --- a/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt +++ b/infra/sync/src/main/java/com/simprints/infra/sync/SyncOrchestratorImpl.kt @@ -57,7 +57,7 @@ internal class SyncOrchestratorImpl @Inject constructor( } } - override suspend fun scheduleBackgroundWork() { + override suspend fun scheduleBackgroundWork(withDelay: Boolean) { if (authStore.signedInProjectId.isNotEmpty()) { workManager.schedulePeriodicWorker( SyncConstants.PROJECT_SYNC_WORK_NAME, @@ -72,7 +72,7 @@ internal class SyncOrchestratorImpl @Inject constructor( SyncConstants.FILE_UP_SYNC_REPEAT_INTERVAL, constraints = getImageUploadConstraints(), ) - rescheduleEventSync() + rescheduleEventSync(withDelay) if (shouldScheduleFirmwareUpdate()) { workManager.schedulePeriodicWorker( SyncConstants.FIRMWARE_UPDATE_WORK_NAME, @@ -110,10 +110,11 @@ internal class SyncOrchestratorImpl @Inject constructor( }.map { } // Converts flow emissions to Unit value as we only care about when it happens, not the value } - override fun rescheduleEventSync() { + override fun rescheduleEventSync(withDelay: Boolean) { workManager.schedulePeriodicWorker( SyncConstants.EVENT_SYNC_WORK_NAME, SyncConstants.EVENT_SYNC_WORKER_INTERVAL, + initialDelay = if (withDelay) SyncConstants.EVENT_SYNC_WORKER_INTERVAL else 0, tags = eventSyncManager.getPeriodicWorkTags(), ) } diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt index 7875468182..fcbeb8ced1 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/SyncOrchestratorImplTest.kt @@ -218,6 +218,21 @@ class SyncOrchestratorImplTest { } } + @Test + fun `reschedules event sync worker with correct delay`() = runTest { + every { eventSyncManager.getPeriodicWorkTags() } returns listOf("tag1", "tag2") + + syncOrchestrator.rescheduleEventSync(true) + + verify { + workManager.enqueueUniquePeriodicWork( + EVENT_SYNC_WORK_NAME, + any(), + match { it.workSpec.initialDelay > 0 }, + ) + } + } + @Test fun `cancel event sync worker cancels correct worker`() = runTest { every { eventSyncManager.getAllWorkerTag() } returns "syncWorkers" From aad730f0198897ae14a7bf83557f97cc2540c595 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Tue, 11 Feb 2025 14:48:15 +0200 Subject: [PATCH 15/22] MS-871 Remove outdates samples when Subject is updated in local database --- .../screen/EnrolLastBiometricViewModel.kt | 2 +- .../usecases/response/EnrolSubjectUseCase.kt | 2 +- .../response/EnrolSubjectUseCaseTest.kt | 2 +- .../EnrolmentRecordRepositoryImpl.kt | 9 +-- .../repository/domain/models/SubjectAction.kt | 2 +- .../EnrolmentRecordLocalDataSourceImpl.kt | 44 +++++++++---- .../EnrolmentRecordRepositoryImplTest.kt | 2 +- .../EnrolmentRecordLocalDataSourceImplTest.kt | 66 +++++++++++++++---- .../sync/down/tasks/EventDownSyncTask.kt | 10 +-- .../sync/down/tasks/EventDownSyncTaskTest.kt | 26 ++++---- 10 files changed, 112 insertions(+), 53 deletions(-) diff --git a/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt b/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt index c34ca748df..0fe8eeeaf9 100644 --- a/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt +++ b/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt @@ -71,7 +71,7 @@ internal class EnrolLastBiometricViewModel @Inject constructor( try { val subject = buildSubject(params) registerEvent(subject) - enrolmentRecordRepository.performActions(listOf(SubjectAction.Creation(subject)), project) + enrolmentRecordRepository.performActions(listOf(SubjectAction.Write(subject)), project) _finish.send(EnrolLastState.Success(subject.subjectId)) } catch (t: Throwable) { Simber.e("Enrolment failed", t, tag = ENROLMENT) diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCase.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCase.kt index 7f318cf907..366f5581a5 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCase.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCase.kt @@ -35,6 +35,6 @@ internal class EnrolSubjectUseCase @Inject constructor( biometricReferenceIds, ), ) - enrolmentRecordRepository.performActions(listOf(SubjectAction.Creation(subject)), project) + enrolmentRecordRepository.performActions(listOf(SubjectAction.Write(subject)), project) } } diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt index eec3751e28..ca3a36c1da 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt @@ -99,7 +99,7 @@ class EnrolSubjectUseCaseTest { coVerify { enrolmentRecordRepository.performActions( withArg { - assertThat(it.first()).isInstanceOf(SubjectAction.Creation::class.java) + assertThat(it.first()).isInstanceOf(SubjectAction.Write::class.java) }, project, ) diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImpl.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImpl.kt index 9fbd8460b1..cbcf346ff6 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImpl.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImpl.kt @@ -95,7 +95,7 @@ internal class EnrolmentRecordRepositoryImpl( project = project, ) return@mapNotNull subject.copy(moduleId = moduleId, attendantId = attendantId) - }.map(SubjectAction::Creation) + }.map(SubjectAction::Write) localDataSource.performActions(tokenizedSubjectsCreateAction, project) } catch (e: Exception) { when (e) { @@ -129,8 +129,8 @@ internal class EnrolmentRecordRepositoryImpl( dataSource: BiometricDataSource, project: Project, onCandidateLoaded: () -> Unit, - ): List = - fromIdentityDataSource(dataSource).loadFingerprintIdentities(query, range, dataSource, project, onCandidateLoaded) + ): List = fromIdentityDataSource(dataSource) + .loadFingerprintIdentities(query, range, dataSource, project, onCandidateLoaded) override suspend fun loadFaceIdentities( query: SubjectQuery, @@ -138,7 +138,8 @@ internal class EnrolmentRecordRepositoryImpl( dataSource: BiometricDataSource, project: Project, onCandidateLoaded: () -> Unit, - ): List = fromIdentityDataSource(dataSource).loadFaceIdentities(query, range, dataSource, project, onCandidateLoaded) + ): List = fromIdentityDataSource(dataSource) + .loadFaceIdentities(query, range, dataSource, project, onCandidateLoaded) private fun fromIdentityDataSource(dataSource: BiometricDataSource) = when (dataSource) { is BiometricDataSource.Simprints -> localDataSource diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectAction.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectAction.kt index 5134ada84e..681d6fd3d9 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectAction.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectAction.kt @@ -4,7 +4,7 @@ import androidx.annotation.Keep @Keep sealed class SubjectAction { - data class Creation( + data class Write( val subject: Subject, ) : SubjectAction() diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImpl.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImpl.kt index 7456378937..ab037c1fd3 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImpl.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImpl.kt @@ -128,24 +128,42 @@ internal class EnrolmentRecordLocalDataSourceImpl @Inject constructor( realmWrapper.writeRealm { realm -> actions.forEach { action -> when (action) { - is SubjectAction.Creation -> { - val tokenizedModuleId = action.subject.moduleId.tokenizeIfNecessary(TokenKeyType.ModuleId, project) - val tokenizedAttendantId = action.subject.attendantId.tokenizeIfNecessary(TokenKeyType.AttendantId, project) - realm.copyToRealm( - action.subject.copy(moduleId = tokenizedModuleId, attendantId = tokenizedAttendantId).fromDomainToDb(), - updatePolicy = UpdatePolicy.ALL, - ) + is SubjectAction.Write -> { + val newSubject = action.subject + .copy( + moduleId = action.subject.moduleId.tokenizeIfNecessary(TokenKeyType.ModuleId, project), + attendantId = action.subject.attendantId.tokenizeIfNecessary(TokenKeyType.AttendantId, project), + ).fromDomainToDb() + + val dbSubject: DbSubject? = realm + .query(DbSubject::class) + .query("$SUBJECT_ID_FIELD == $0", newSubject.subjectId) + .first() + .find() + + if (dbSubject != null) { + // When updating an existing subject, we must manually delete outdated samples + val fingerprintSampleIds = newSubject.fingerprintSamples.map { it.id }.toSet() + dbSubject.fingerprintSamples + .filterNot { it.id in fingerprintSampleIds } + .takeIf { it.isNotEmpty() } + ?.forEach { realm.delete(it) } + + val faceSampleIds = newSubject.faceSamples.map { it.id }.toSet() + dbSubject.faceSamples + .filterNot { it.id in faceSampleIds } + .takeIf { it.isNotEmpty() } + ?.forEach { realm.delete(it) } + } + + realm.copyToRealm(newSubject, updatePolicy = UpdatePolicy.ALL) } is SubjectAction.Deletion -> realm.delete( realm .query(DbSubject::class) - .buildRealmQueryForSubject( - query = SubjectQuery( - subjectId = - action.subjectId, - ), - ).find(), + .buildRealmQueryForSubject(query = SubjectQuery(subjectId = action.subjectId)) + .find(), ) } } diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImplTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImplTest.kt index 67b600aa85..7438784e6d 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImplTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImplTest.kt @@ -214,7 +214,7 @@ class EnrolmentRecordRepositoryImplTest { attendantId = attendantIdTokenized, moduleId = moduleIdTokenized, ) - val expectedSubjectActions = listOf(SubjectAction.Creation(expectedSubject)) + val expectedSubjectActions = listOf(SubjectAction.Write(expectedSubject)) coVerify { localDataSource.performActions(expectedSubjectActions, project) } } diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt index be2c16f205..a8984a083f 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt @@ -2,11 +2,15 @@ package com.simprints.infra.enrolment.records.repository.local import com.google.common.truth.Truth.assertThat import com.simprints.core.domain.face.FaceSample +import com.simprints.core.domain.fingerprint.FingerprintSample +import com.simprints.core.domain.fingerprint.IFingerIdentifier import com.simprints.core.domain.tokenization.asTokenizableEncrypted import com.simprints.core.domain.tokenization.asTokenizableRaw import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.store.tokenization.TokenizationProcessor import com.simprints.infra.enrolment.records.realm.store.RealmWrapper +import com.simprints.infra.enrolment.records.realm.store.models.DbFaceSample +import com.simprints.infra.enrolment.records.realm.store.models.DbFingerprintSample import com.simprints.infra.enrolment.records.realm.store.models.DbSubject import com.simprints.infra.enrolment.records.repository.domain.models.BiometricDataSource import com.simprints.infra.enrolment.records.repository.domain.models.Subject @@ -28,6 +32,7 @@ import io.mockk.verify import io.realm.kotlin.MutableRealm import io.realm.kotlin.Realm import io.realm.kotlin.query.RealmQuery +import io.realm.kotlin.query.RealmSingleQuery import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -47,6 +52,9 @@ class EnrolmentRecordLocalDataSourceImplTest { @MockK private lateinit var realmQuery: RealmQuery + @MockK + private lateinit var realmSingleQuery: RealmSingleQuery + @MockK private lateinit var tokenizationProcessor: TokenizationProcessor @@ -88,7 +96,13 @@ class EnrolmentRecordLocalDataSourceImplTest { every { realm.query(DbSubject::class) } returns realmQuery every { mutableRealm.query(DbSubject::class) } returns realmQuery - enrolmentRecordLocalDataSource = EnrolmentRecordLocalDataSourceImpl(realmWrapperMock, tokenizationProcessor) + every { realmQuery.query(any(), any()) } returns realmQuery + every { realmQuery.first() } returns realmSingleQuery + + enrolmentRecordLocalDataSource = EnrolmentRecordLocalDataSourceImpl( + realmWrapperMock, + tokenizationProcessor, + ) } @Test @@ -212,10 +226,7 @@ class EnrolmentRecordLocalDataSourceImplTest { val savedPersons = saveFakePeople(getRandomPeople(20)) val fakePerson = savedPersons[0].fromDomainToDb() - val people = - enrolmentRecordLocalDataSource - .load(SubjectQuery(attendantId = savedPersons[0].attendantId)) - .toList() + val people = enrolmentRecordLocalDataSource.load(SubjectQuery(attendantId = savedPersons[0].attendantId)).toList() listOf(fakePerson).zip(people).forEach { (dbSubject, subject) -> assertThat(dbSubject.deepEquals(subject.fromDomainToDb())).isTrue() } @@ -226,10 +237,7 @@ class EnrolmentRecordLocalDataSourceImplTest { val savedPersons = saveFakePeople(getRandomPeople(20)) val fakePerson = savedPersons[0].fromDomainToDb() - val people = - enrolmentRecordLocalDataSource - .load(SubjectQuery(moduleId = fakePerson.moduleId.asTokenizableEncrypted())) - .toList() + val people = enrolmentRecordLocalDataSource.load(SubjectQuery(moduleId = fakePerson.moduleId.asTokenizableEncrypted())).toList() listOf(fakePerson).zip(people).forEach { (dbSubject, subject) -> assertThat(dbSubject.deepEquals(subject.fromDomainToDb())).isTrue() } @@ -238,10 +246,38 @@ class EnrolmentRecordLocalDataSourceImplTest { @Test fun performSubjectCreationAction() = runTest { val subject = getFakePerson() + every { realmSingleQuery.find() } returns null + + enrolmentRecordLocalDataSource.performActions( + listOf(SubjectAction.Write(subject.fromDbToDomain())), + project, + ) + val peopleCount = enrolmentRecordLocalDataSource.count() + assertThat(peopleCount).isEqualTo(1) + } + + @Test + fun performSubjectCreationAction_deletesOldSamples() = runTest { + every { realmSingleQuery.find() } returns getRandomSubject() + .copy( + faceSamples = listOf( + getRandomFaceSample("faceToDelete"), + ), + fingerprintSamples = listOf( + getRandomFingerprintSample("fingerToDelete"), + ), + ).fromDomainToDb() + val subject = getFakePerson() + enrolmentRecordLocalDataSource.performActions( - listOf(SubjectAction.Creation(subject.fromDbToDomain())), + listOf(SubjectAction.Write(subject.fromDbToDomain())), project, ) + + verify { + mutableRealm.delete(withArg { it.id == "faceToDelete" }) + mutableRealm.delete(withArg { it.id == "faceToDelete" }) + } val peopleCount = enrolmentRecordLocalDataSource.count() assertThat(peopleCount).isEqualTo(1) } @@ -308,8 +344,8 @@ class EnrolmentRecordLocalDataSourceImplTest { userId: String = UUID.randomUUID().toString(), moduleId: String = UUID.randomUUID().toString(), faceSamples: Array = arrayOf( - FaceSample(Random.nextBytes(64), "faceTemplateFormat", "referenceId"), - FaceSample(Random.nextBytes(64), "faceTemplateFormat", "referenceId"), + getRandomFaceSample(), + getRandomFaceSample(), ), ): Subject = Subject( subjectId = patientId, @@ -318,4 +354,10 @@ class EnrolmentRecordLocalDataSourceImplTest { moduleId = moduleId.asTokenizableRaw(), faceSamples = faceSamples.toList(), ) + + private fun getRandomFaceSample(id: String = UUID.randomUUID().toString()) = + FaceSample(Random.nextBytes(64), "faceTemplateFormat", "referenceId", id) + + private fun getRandomFingerprintSample(id: String = UUID.randomUUID().toString()) = + FingerprintSample(IFingerIdentifier.LEFT_3RD_FINGER, Random.nextBytes(64), 42, "fingerprintTemplateFormat", "referenceId", id) } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt index 755faabcc4..d9371dbdb9 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt @@ -9,8 +9,8 @@ import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.sync.ConfigManager import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction -import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Creation import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Deletion +import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Write import com.simprints.infra.enrolment.records.repository.domain.models.SubjectQuery import com.simprints.infra.events.EventRepository import com.simprints.infra.events.event.domain.models.downsync.EventDownSyncRequestEvent @@ -194,7 +194,7 @@ internal class EventDownSyncTask @Inject constructor( fun handleSubjectCreationEvent(event: EnrolmentRecordCreationEvent): List { val subject = subjectFactory.buildSubjectFromCreationPayload(event.payload) return if (subject.fingerprintSamples.isNotEmpty() || subject.faceSamples.isNotEmpty()) { - listOf(Creation(subject)) + listOf(Write(subject)) } else { emptyList() } @@ -247,11 +247,11 @@ internal class EventDownSyncTask @Inject constructor( private fun EventDownSyncOperation.isSyncingByAttendant(): Boolean = !queryEvent.attendantId.isNullOrEmpty() - private fun createASubjectActionFromRecordCreation(enrolmentRecordCreation: EnrolmentRecordCreationInMove?): Creation? = + private fun createASubjectActionFromRecordCreation(enrolmentRecordCreation: EnrolmentRecordCreationInMove?): Write? = enrolmentRecordCreation?.let { val subject = subjectFactory.buildSubjectFromMovePayload(it) if (subject.fingerprintSamples.isNotEmpty() || subject.faceSamples.isNotEmpty()) { - Creation(subject) + Write(subject) } else { null } @@ -265,7 +265,7 @@ internal class EventDownSyncTask @Inject constructor( val subject = subjectFactory.buildSubjectFromUpdatePayload(existingSubject, event.payload) return if (subject.fingerprintSamples.isNotEmpty() || subject.faceSamples.isNotEmpty()) { - listOf(Creation(subject)) + listOf(Write(subject)) } else { // Having no samples after update is equal to deletion listOf(Deletion(event.payload.subjectId)) diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt index 53710559fe..e35fea7312 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt @@ -10,8 +10,8 @@ import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.sync.ConfigManager import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository import com.simprints.infra.enrolment.records.repository.domain.models.Subject -import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Creation import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Deletion +import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Write import com.simprints.infra.events.EventRepository import com.simprints.infra.events.event.domain.models.downsync.EventDownSyncRequestEvent import com.simprints.infra.events.event.domain.models.scope.EventScope @@ -265,7 +265,7 @@ class EventDownSyncTaskTest { coVerify { enrolmentRecordRepository.performActions( listOf( - Creation( + Write( subjectFactory.buildSubjectFromCreationPayload( event.payload, ), @@ -344,7 +344,7 @@ class EventDownSyncTaskTest { enrolmentRecordRepository.performActions( listOf( Deletion(eventToMoveToModule2.payload.enrolmentRecordDeletion.subjectId), - Creation(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), + Write(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), ), project, ) @@ -372,7 +372,7 @@ class EventDownSyncTaskTest { enrolmentRecordRepository.performActions( listOf( Deletion(eventToMoveToModule2.payload.enrolmentRecordDeletion.subjectId), - Creation(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), + Write(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), ), project, ) @@ -422,7 +422,7 @@ class EventDownSyncTaskTest { enrolmentRecordRepository.performActions( listOf( Deletion(eventToMoveToModule2.payload.enrolmentRecordDeletion.subjectId), - Creation(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), + Write(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), ), project, ) @@ -450,7 +450,7 @@ class EventDownSyncTaskTest { enrolmentRecordRepository.performActions( listOf( Deletion(eventToMoveToModule2.payload.enrolmentRecordDeletion.subjectId), - Creation(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), + Write(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), ), project, ) @@ -466,9 +466,7 @@ class EventDownSyncTaskTest { coVerify { enrolmentRecordRepository.performActions( - listOf( - Deletion(eventToMoveToAttendant2.payload.enrolmentRecordDeletion.subjectId), - ), + listOf(Deletion(eventToMoveToAttendant2.payload.enrolmentRecordDeletion.subjectId)), project, ) } @@ -485,7 +483,7 @@ class EventDownSyncTaskTest { enrolmentRecordRepository.performActions( listOf( Deletion(eventToMoveToAttendant2.payload.enrolmentRecordDeletion.subjectId), - Creation(subjectFactory.buildSubjectFromMovePayload(eventToMoveToAttendant2.payload.enrolmentRecordCreation)), + Write(subjectFactory.buildSubjectFromMovePayload(eventToMoveToAttendant2.payload.enrolmentRecordCreation)), ), project, ) @@ -503,7 +501,7 @@ class EventDownSyncTaskTest { enrolmentRecordRepository.performActions( listOf( Deletion(eventToMoveToModule2.payload.enrolmentRecordDeletion.subjectId), - Creation(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), + Write(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), ), project, ) @@ -531,8 +529,8 @@ class EventDownSyncTaskTest { coVerify { enrolmentRecordRepository.performActions( - withArg { actions -> actions.all { it is Creation } }, - project, + withArg { actions -> actions.all { it is Write } }, + any(), ) } } @@ -559,7 +557,7 @@ class EventDownSyncTaskTest { coVerify { enrolmentRecordRepository.performActions( withArg { actions -> actions.all { it is Deletion } }, - project, + any(), ) } } From abf52639d8292d423dcddcfd6eaca2a32bbf067d Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 12 Feb 2025 13:53:54 +0200 Subject: [PATCH 16/22] MS-871 Allow reference creation be synced as biometric event --- .../infra/eventsync/sync/up/tasks/EventUpSyncTask.kt | 4 +++- .../infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt index 34152a81bb..21a61caca8 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTask.kt @@ -14,6 +14,7 @@ import com.simprints.infra.config.store.models.canSyncAnalyticsDataToSimprints import com.simprints.infra.config.store.models.canSyncBiometricDataToSimprints import com.simprints.infra.config.sync.ConfigManager import com.simprints.infra.events.EventRepository +import com.simprints.infra.events.event.domain.models.BiometricReferenceCreationEvent import com.simprints.infra.events.event.domain.models.EnrolmentEventV2 import com.simprints.infra.events.event.domain.models.EnrolmentEventV4 import com.simprints.infra.events.event.domain.models.Event @@ -319,7 +320,8 @@ internal class EventUpSyncTask @Inject constructor( it is EnrolmentEventV4 || it is PersonCreationEvent || it is FingerprintCaptureBiometricsEvent || - it is FaceCaptureBiometricsEvent + it is FaceCaptureBiometricsEvent || + it is BiometricReferenceCreationEvent } config.canSyncAnalyticsDataToSimprints() -> events.filterNot { diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt index ebc9c36b3d..36e407087b 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/up/tasks/EventUpSyncTaskTest.kt @@ -25,6 +25,7 @@ import com.simprints.infra.events.sampledata.SampleDefaults.GUID2 import com.simprints.infra.events.sampledata.SampleDefaults.GUID3 import com.simprints.infra.events.sampledata.createAlertScreenEvent import com.simprints.infra.events.sampledata.createAuthenticationEvent +import com.simprints.infra.events.sampledata.createBiometricReferenceCreationEvent import com.simprints.infra.events.sampledata.createEnrolmentEventV2 import com.simprints.infra.events.sampledata.createEnrolmentEventV4 import com.simprints.infra.events.sampledata.createEventWithSessionId @@ -299,6 +300,7 @@ internal class EventUpSyncTaskTest { createEnrolmentEventV2(), createEnrolmentEventV4(), createAlertScreenEvent(), + createBiometricReferenceCreationEvent(), ) eventUpSyncTask.upSync(operation, eventScope).toList() @@ -307,7 +309,7 @@ internal class EventUpSyncTaskTest { coVerify(exactly = 1) { mapDomainEventScopeToApiUseCase(any(), capture(capturedRequest), any()) } - assertThat(capturedRequest.captured).hasSize(4) + assertThat(capturedRequest.captured).hasSize(5) } @Test From c3b9797bdb697e8ae1593f392425f9b17667f561 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 12 Feb 2025 14:29:14 +0200 Subject: [PATCH 17/22] MS-871 Move update logic into the realm transaction --- .../repository/domain/models/SubjectAction.kt | 9 +++ .../EnrolmentRecordLocalDataSourceImpl.kt | 35 +++++++++-- .../EnrolmentRecordLocalDataSourceImplTest.kt | 61 +++++++++++++++++-- .../sync/down/tasks/EventDownSyncTask.kt | 20 +++--- .../sync/down/tasks/SubjectFactory.kt | 4 +- 5 files changed, 104 insertions(+), 25 deletions(-) diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectAction.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectAction.kt index 681d6fd3d9..c97e57f43a 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectAction.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectAction.kt @@ -1,6 +1,8 @@ package com.simprints.infra.enrolment.records.repository.domain.models import androidx.annotation.Keep +import com.simprints.core.domain.face.FaceSample +import com.simprints.core.domain.fingerprint.FingerprintSample @Keep sealed class SubjectAction { @@ -8,6 +10,13 @@ sealed class SubjectAction { val subject: Subject, ) : SubjectAction() + data class Update( + val subjectId: String, + val faceSamplesToAdd: List, + val fingerprintSamplesToAdd: List, + val referenceIdsToRemove: List, + ) : SubjectAction() + data class Deletion( val subjectId: String, ) : SubjectAction() diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImpl.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImpl.kt index ab037c1fd3..291cf3565a 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImpl.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImpl.kt @@ -18,7 +18,9 @@ import com.simprints.infra.enrolment.records.repository.local.models.fromDbToDom import com.simprints.infra.enrolment.records.repository.local.models.fromDomainToDb import com.simprints.infra.logging.LoggingConstants.CrashReportTag.REALM_DB import com.simprints.infra.logging.Simber +import io.realm.kotlin.MutableRealm import io.realm.kotlin.UpdatePolicy +import io.realm.kotlin.ext.toRealmList import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.query.Sort import io.realm.kotlin.query.find @@ -134,12 +136,7 @@ internal class EnrolmentRecordLocalDataSourceImpl @Inject constructor( moduleId = action.subject.moduleId.tokenizeIfNecessary(TokenKeyType.ModuleId, project), attendantId = action.subject.attendantId.tokenizeIfNecessary(TokenKeyType.AttendantId, project), ).fromDomainToDb() - - val dbSubject: DbSubject? = realm - .query(DbSubject::class) - .query("$SUBJECT_ID_FIELD == $0", newSubject.subjectId) - .first() - .find() + val dbSubject: DbSubject? = realm.findSubject(newSubject.subjectId) if (dbSubject != null) { // When updating an existing subject, we must manually delete outdated samples @@ -159,6 +156,29 @@ internal class EnrolmentRecordLocalDataSourceImpl @Inject constructor( realm.copyToRealm(newSubject, updatePolicy = UpdatePolicy.ALL) } + is SubjectAction.Update -> { + val dbSubject: DbSubject? = realm.findSubject(RealmUUID.from(action.subjectId)) + if (dbSubject != null) { + val referencesToDelete = action.referenceIdsToRemove.toSet() // to make lookup O(1) + val faceSamplesMap = dbSubject.faceSamples.groupBy { it.referenceId in referencesToDelete } + val fingerprintSamplesMap = dbSubject.fingerprintSamples.groupBy { it.referenceId in referencesToDelete } + + // Append new samples to the list of samples that remain after removing + dbSubject.faceSamples = ( + faceSamplesMap[false].orEmpty() + action.faceSamplesToAdd.map { it.fromDomainToDb() } + ).toRealmList() + dbSubject.fingerprintSamples = ( + fingerprintSamplesMap[false].orEmpty() + action.fingerprintSamplesToAdd.map { it.fromDomainToDb() } + ).toRealmList() + + faceSamplesMap[true]?.forEach { realm.delete(it) } + fingerprintSamplesMap[true]?.forEach { realm.delete(it) } + realm.copyToRealm(dbSubject, updatePolicy = UpdatePolicy.ALL) + } else { + Simber.i("[SubjectLocalDataSourceImpl] Subject not found for update", tag = REALM_DB) + } + } + is SubjectAction.Deletion -> realm.delete( realm .query(DbSubject::class) @@ -183,6 +203,9 @@ internal class EnrolmentRecordLocalDataSourceImpl @Inject constructor( is TokenizableString.Tokenized -> this } + private fun MutableRealm.findSubject(subjectId: RealmUUID): DbSubject? = + query(DbSubject::class).query("$SUBJECT_ID_FIELD == $0", subjectId).first().find() + private fun RealmQuery.buildRealmQueryForSubject(query: SubjectQuery): RealmQuery { var realmQuery = this diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt index a8984a083f..eb2f7af643 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt @@ -282,6 +282,49 @@ class EnrolmentRecordLocalDataSourceImplTest { assertThat(peopleCount).isEqualTo(1) } + @Test + fun performSubjectUpdateAction() = runTest { + val subject = getFakePerson() + every { realmSingleQuery.find() } returns getRandomSubject( + faceSamples = listOf( + getRandomFaceSample(referenceId = "faceToDelete"), + getRandomFaceSample(), + ), + fingerprintSamples = listOf( + getRandomFingerprintSample(referenceId = "fingerToDelete"), + getRandomFingerprintSample(), + ), + ).fromDomainToDb() + + enrolmentRecordLocalDataSource.performActions( + listOf( + SubjectAction.Update( + subject.subjectId.toString(), + faceSamplesToAdd = listOf(getRandomFaceSample()), + fingerprintSamplesToAdd = listOf(getRandomFingerprintSample()), + referenceIdsToRemove = listOf("faceToDelete", "fingerToDelete"), + ), + ), + project, + ) + val peopleCount = enrolmentRecordLocalDataSource.count() + assertThat(peopleCount).isEqualTo(1) + verify { + mutableRealm.delete(withArg { it.id == "faceToDelete" }) + mutableRealm.delete(withArg { it.id == "faceToDelete" }) + mutableRealm.copyToRealm( + withArg { + // one old + one new + it.faceSamples.size == 2 && + it.fingerprintSamples.size == 2 && + it.faceSamples.none { it.referenceId == "faceToDelete" } && + it.fingerprintSamples.none { it.referenceId == "fingerToDelete" } + }, + any(), + ) + } + } + @Test fun performSubjectDeletionAction() = runTest { val subject = getFakePerson() @@ -343,21 +386,27 @@ class EnrolmentRecordLocalDataSourceImplTest { projectId: String = UUID.randomUUID().toString(), userId: String = UUID.randomUUID().toString(), moduleId: String = UUID.randomUUID().toString(), - faceSamples: Array = arrayOf( + faceSamples: List = listOf( getRandomFaceSample(), getRandomFaceSample(), ), + fingerprintSamples: List = listOf(), ): Subject = Subject( subjectId = patientId, projectId = projectId, attendantId = userId.asTokenizableRaw(), moduleId = moduleId.asTokenizableRaw(), - faceSamples = faceSamples.toList(), + faceSamples = faceSamples, + fingerprintSamples = fingerprintSamples, ) - private fun getRandomFaceSample(id: String = UUID.randomUUID().toString()) = - FaceSample(Random.nextBytes(64), "faceTemplateFormat", "referenceId", id) + private fun getRandomFaceSample( + id: String = UUID.randomUUID().toString(), + referenceId: String = "referenceId", + ) = FaceSample(Random.nextBytes(64), "faceTemplateFormat", referenceId, id) - private fun getRandomFingerprintSample(id: String = UUID.randomUUID().toString()) = - FingerprintSample(IFingerIdentifier.LEFT_3RD_FINGER, Random.nextBytes(64), 42, "fingerprintTemplateFormat", "referenceId", id) + private fun getRandomFingerprintSample( + id: String = UUID.randomUUID().toString(), + referenceId: String = "referenceId", + ) = FingerprintSample(IFingerIdentifier.LEFT_3RD_FINGER, Random.nextBytes(64), 42, "fingerprintTemplateFormat", referenceId, id) } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt index d9371dbdb9..1a10ff0531 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt @@ -11,7 +11,6 @@ import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepositor import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Deletion import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Write -import com.simprints.infra.enrolment.records.repository.domain.models.SubjectQuery import com.simprints.infra.events.EventRepository import com.simprints.infra.events.event.domain.models.downsync.EventDownSyncRequestEvent import com.simprints.infra.events.event.domain.models.scope.EventScope @@ -260,16 +259,15 @@ internal class EventDownSyncTask @Inject constructor( private fun handleSubjectDeletionEvent(event: EnrolmentRecordDeletionEvent): List = listOf(Deletion(event.payload.subjectId)) - private suspend fun handleSubjectUpdateEvent(event: EnrolmentRecordUpdateEvent): List { - val existingSubject = enrolmentRecordRepository.load(SubjectQuery(subjectId = event.payload.subjectId)).first() - val subject = subjectFactory.buildSubjectFromUpdatePayload(existingSubject, event.payload) - - return if (subject.fingerprintSamples.isNotEmpty() || subject.faceSamples.isNotEmpty()) { - listOf(Write(subject)) - } else { - // Having no samples after update is equal to deletion - listOf(Deletion(event.payload.subjectId)) - } + private fun handleSubjectUpdateEvent(event: EnrolmentRecordUpdateEvent): List = with(event.payload) { + listOf( + SubjectAction.Update( + subjectId = subjectId, + faceSamplesToAdd = subjectFactory.extractFaceSamplesFromBiometricReferences(biometricReferencesAdded), + fingerprintSamplesToAdd = subjectFactory.extractFingerprintSamplesFromBiometricReferences(biometricReferencesAdded), + referenceIdsToRemove = biometricReferencesRemoved, + ), + ) } companion object { diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt index 8f6d53d99b..e33619908a 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt @@ -120,7 +120,7 @@ class SubjectFactory @Inject constructor( .mapNotNull { it.sample } .map { FaceSample(it.template, it.format, faceResponse.referenceId) } - private fun extractFingerprintSamplesFromBiometricReferences(biometricReferences: List?) = biometricReferences + fun extractFingerprintSamplesFromBiometricReferences(biometricReferences: List?) = biometricReferences ?.filterIsInstance() ?.firstOrNull() ?.let { reference -> reference.templates.map { buildFingerprintSample(it, reference.format, reference.id) } } @@ -138,7 +138,7 @@ class SubjectFactory @Inject constructor( referenceId = referenceId, ) - private fun extractFaceSamplesFromBiometricReferences(biometricReferences: List?) = biometricReferences + fun extractFaceSamplesFromBiometricReferences(biometricReferences: List?) = biometricReferences ?.filterIsInstance() ?.firstOrNull() ?.let { reference -> reference.templates.map { buildFaceSample(it, reference.format, reference.id) } } From 6f7d1cad367025432a5b0ee569f8b7f47ff1fa61 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 12 Feb 2025 15:09:59 +0200 Subject: [PATCH 18/22] MS-871 Fix update event API model --- .../models/subject/ApiEnrolmentRecordUpdatePayload.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordUpdatePayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordUpdatePayload.kt index 14ed2e73a6..57014fc4ef 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordUpdatePayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordUpdatePayload.kt @@ -8,12 +8,12 @@ import com.simprints.infra.eventsync.event.remote.models.subject.biometricref.fr @Keep internal data class ApiEnrolmentRecordUpdatePayload( val subjectId: String, - val biometricReferencesAdded: List, - val biometricReferencesRemoved: List, -) : ApiEnrolmentRecordEventPayload(ApiEnrolmentRecordPayloadType.EnrolmentRecordDeletion) + val biometricReferencesAdded: List?, + val biometricReferencesRemoved: List?, +) : ApiEnrolmentRecordEventPayload(ApiEnrolmentRecordPayloadType.EnrolmentRecordUpdate) internal fun ApiEnrolmentRecordUpdatePayload.fromApiToDomain() = EnrolmentRecordUpdateEvent.EnrolmentRecordUpdatePayload( subjectId, - biometricReferencesAdded.map { it.fromApiToDomain() }, - biometricReferencesRemoved, + biometricReferencesAdded?.map { it.fromApiToDomain() }.orEmpty(), + biometricReferencesRemoved.orEmpty(), ) From 4fad11eea4a305e0d36696ba7a2c46f51612ee1b Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 12 Feb 2025 15:37:08 +0200 Subject: [PATCH 19/22] MS-871 Restore old SubjectAction name back to Creation --- .../screen/EnrolLastBiometricViewModel.kt | 2 +- .../usecases/response/EnrolSubjectUseCase.kt | 2 +- .../response/EnrolSubjectUseCaseTest.kt | 2 +- .../EnrolmentRecordRepositoryImpl.kt | 2 +- .../repository/domain/models/SubjectAction.kt | 2 +- .../EnrolmentRecordLocalDataSourceImpl.kt | 2 +- .../EnrolmentRecordRepositoryImplTest.kt | 76 +++++++++++++++---- .../EnrolmentRecordLocalDataSourceImplTest.kt | 4 +- .../sync/down/tasks/EventDownSyncTask.kt | 8 +- .../sync/down/tasks/EventDownSyncTaskTest.kt | 18 ++--- 10 files changed, 84 insertions(+), 34 deletions(-) diff --git a/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt b/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt index 0fe8eeeaf9..c34ca748df 100644 --- a/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt +++ b/feature/enrol-last-biometric/src/main/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModel.kt @@ -71,7 +71,7 @@ internal class EnrolLastBiometricViewModel @Inject constructor( try { val subject = buildSubject(params) registerEvent(subject) - enrolmentRecordRepository.performActions(listOf(SubjectAction.Write(subject)), project) + enrolmentRecordRepository.performActions(listOf(SubjectAction.Creation(subject)), project) _finish.send(EnrolLastState.Success(subject.subjectId)) } catch (t: Throwable) { Simber.e("Enrolment failed", t, tag = ENROLMENT) diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCase.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCase.kt index 366f5581a5..7f318cf907 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCase.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCase.kt @@ -35,6 +35,6 @@ internal class EnrolSubjectUseCase @Inject constructor( biometricReferenceIds, ), ) - enrolmentRecordRepository.performActions(listOf(SubjectAction.Write(subject)), project) + enrolmentRecordRepository.performActions(listOf(SubjectAction.Creation(subject)), project) } } diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt index ca3a36c1da..eec3751e28 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt @@ -99,7 +99,7 @@ class EnrolSubjectUseCaseTest { coVerify { enrolmentRecordRepository.performActions( withArg { - assertThat(it.first()).isInstanceOf(SubjectAction.Write::class.java) + assertThat(it.first()).isInstanceOf(SubjectAction.Creation::class.java) }, project, ) diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImpl.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImpl.kt index cbcf346ff6..0c89d95022 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImpl.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImpl.kt @@ -95,7 +95,7 @@ internal class EnrolmentRecordRepositoryImpl( project = project, ) return@mapNotNull subject.copy(moduleId = moduleId, attendantId = attendantId) - }.map(SubjectAction::Write) + }.map(SubjectAction::Creation) localDataSource.performActions(tokenizedSubjectsCreateAction, project) } catch (e: Exception) { when (e) { diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectAction.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectAction.kt index c97e57f43a..421a6f11c6 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectAction.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectAction.kt @@ -6,7 +6,7 @@ import com.simprints.core.domain.fingerprint.FingerprintSample @Keep sealed class SubjectAction { - data class Write( + data class Creation( val subject: Subject, ) : SubjectAction() diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImpl.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImpl.kt index 291cf3565a..086bceca59 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImpl.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImpl.kt @@ -130,7 +130,7 @@ internal class EnrolmentRecordLocalDataSourceImpl @Inject constructor( realmWrapper.writeRealm { realm -> actions.forEach { action -> when (action) { - is SubjectAction.Write -> { + is SubjectAction.Creation -> { val newSubject = action.subject .copy( moduleId = action.subject.moduleId.tokenizeIfNecessary(TokenKeyType.ModuleId, project), diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImplTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImplTest.kt index 7438784e6d..24484216ad 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImplTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/EnrolmentRecordRepositoryImplTest.kt @@ -50,7 +50,7 @@ class EnrolmentRecordRepositoryImplTest { } } - private val onCandidateLoaded: () -> Unit = {} + private val onCandidateLoaded: () -> Unit = {} private val tokenizationProcessor = mockk() private val localDataSource = mockk(relaxed = true) private val commCareDataSource = mockk(relaxed = true) @@ -214,7 +214,7 @@ class EnrolmentRecordRepositoryImplTest { attendantId = attendantIdTokenized, moduleId = moduleIdTokenized, ) - val expectedSubjectActions = listOf(SubjectAction.Write(expectedSubject)) + val expectedSubjectActions = listOf(SubjectAction.Creation(expectedSubject)) coVerify { localDataSource.performActions(expectedSubjectActions, project) } } @@ -278,18 +278,34 @@ class EnrolmentRecordRepositoryImplTest { val expectedSubjectQuery = SubjectQuery() val expectedRange = 0..10 val expectedFingerprintIdentities = listOf() - coEvery { localDataSource.loadFingerprintIdentities(expectedSubjectQuery, expectedRange, any(), project, onCandidateLoaded) } returns expectedFingerprintIdentities + coEvery { + localDataSource.loadFingerprintIdentities( + expectedSubjectQuery, + expectedRange, + any(), + project, + onCandidateLoaded, + ) + } returns expectedFingerprintIdentities val fingerprintIdentities = repository.loadFingerprintIdentities( query = expectedSubjectQuery, range = expectedRange, dataSource = BiometricDataSource.Simprints, project = project, - onCandidateLoaded = onCandidateLoaded + onCandidateLoaded = onCandidateLoaded, ) assert(fingerprintIdentities == expectedFingerprintIdentities) - coVerify(exactly = 1) { localDataSource.loadFingerprintIdentities(expectedSubjectQuery, expectedRange, any(), project, onCandidateLoaded) } + coVerify(exactly = 1) { + localDataSource.loadFingerprintIdentities( + expectedSubjectQuery, + expectedRange, + any(), + project, + onCandidateLoaded, + ) + } } @Test @@ -297,18 +313,34 @@ class EnrolmentRecordRepositoryImplTest { val expectedSubjectQuery = SubjectQuery() val expectedRange = 0..10 val expectedFingerprintIdentities = listOf() - coEvery { commCareDataSource.loadFingerprintIdentities(expectedSubjectQuery, expectedRange, any(), project, onCandidateLoaded) } returns expectedFingerprintIdentities + coEvery { + commCareDataSource.loadFingerprintIdentities( + expectedSubjectQuery, + expectedRange, + any(), + project, + onCandidateLoaded, + ) + } returns expectedFingerprintIdentities val fingerprintIdentities = repository.loadFingerprintIdentities( query = expectedSubjectQuery, range = expectedRange, dataSource = BiometricDataSource.CommCare(""), project = project, - onCandidateLoaded = onCandidateLoaded + onCandidateLoaded = onCandidateLoaded, ) assert(fingerprintIdentities == expectedFingerprintIdentities) - coVerify(exactly = 1) { commCareDataSource.loadFingerprintIdentities(expectedSubjectQuery, expectedRange, any(), project, onCandidateLoaded) } + coVerify(exactly = 1) { + commCareDataSource.loadFingerprintIdentities( + expectedSubjectQuery, + expectedRange, + any(), + project, + onCandidateLoaded, + ) + } } @Test @@ -316,14 +348,22 @@ class EnrolmentRecordRepositoryImplTest { val expectedSubjectQuery = SubjectQuery() val expectedRange = 0..10 val expectedFaceIdentities = listOf() - coEvery { localDataSource.loadFaceIdentities(expectedSubjectQuery, expectedRange, any(), project, onCandidateLoaded) } returns expectedFaceIdentities + coEvery { + localDataSource.loadFaceIdentities( + expectedSubjectQuery, + expectedRange, + any(), + project, + onCandidateLoaded, + ) + } returns expectedFaceIdentities val faceIdentities = repository.loadFaceIdentities( query = expectedSubjectQuery, range = expectedRange, dataSource = BiometricDataSource.Simprints, project = project, - onCandidateLoaded = onCandidateLoaded + onCandidateLoaded = onCandidateLoaded, ) assert(faceIdentities == expectedFaceIdentities) @@ -335,17 +375,27 @@ class EnrolmentRecordRepositoryImplTest { val expectedSubjectQuery = SubjectQuery() val expectedRange = 0..10 val expectedFaceIdentities = listOf() - coEvery { commCareDataSource.loadFaceIdentities(expectedSubjectQuery, expectedRange, any(), project, onCandidateLoaded) } returns expectedFaceIdentities + coEvery { + commCareDataSource.loadFaceIdentities(expectedSubjectQuery, expectedRange, any(), project, onCandidateLoaded) + } returns expectedFaceIdentities val faceIdentities = repository.loadFaceIdentities( query = expectedSubjectQuery, range = expectedRange, dataSource = BiometricDataSource.CommCare(""), project = project, - onCandidateLoaded = onCandidateLoaded + onCandidateLoaded = onCandidateLoaded, ) assert(faceIdentities == expectedFaceIdentities) - coVerify(exactly = 1) { commCareDataSource.loadFaceIdentities(expectedSubjectQuery, expectedRange, any(), project, onCandidateLoaded) } + coVerify(exactly = 1) { + commCareDataSource.loadFaceIdentities( + expectedSubjectQuery, + expectedRange, + any(), + project, + onCandidateLoaded, + ) + } } } diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt index eb2f7af643..09838d6906 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/EnrolmentRecordLocalDataSourceImplTest.kt @@ -249,7 +249,7 @@ class EnrolmentRecordLocalDataSourceImplTest { every { realmSingleQuery.find() } returns null enrolmentRecordLocalDataSource.performActions( - listOf(SubjectAction.Write(subject.fromDbToDomain())), + listOf(SubjectAction.Creation(subject.fromDbToDomain())), project, ) val peopleCount = enrolmentRecordLocalDataSource.count() @@ -270,7 +270,7 @@ class EnrolmentRecordLocalDataSourceImplTest { val subject = getFakePerson() enrolmentRecordLocalDataSource.performActions( - listOf(SubjectAction.Write(subject.fromDbToDomain())), + listOf(SubjectAction.Creation(subject.fromDbToDomain())), project, ) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt index 1a10ff0531..1539651746 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTask.kt @@ -9,8 +9,8 @@ import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.sync.ConfigManager import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction +import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Creation import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Deletion -import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Write import com.simprints.infra.events.EventRepository import com.simprints.infra.events.event.domain.models.downsync.EventDownSyncRequestEvent import com.simprints.infra.events.event.domain.models.scope.EventScope @@ -193,7 +193,7 @@ internal class EventDownSyncTask @Inject constructor( fun handleSubjectCreationEvent(event: EnrolmentRecordCreationEvent): List { val subject = subjectFactory.buildSubjectFromCreationPayload(event.payload) return if (subject.fingerprintSamples.isNotEmpty() || subject.faceSamples.isNotEmpty()) { - listOf(Write(subject)) + listOf(Creation(subject)) } else { emptyList() } @@ -246,11 +246,11 @@ internal class EventDownSyncTask @Inject constructor( private fun EventDownSyncOperation.isSyncingByAttendant(): Boolean = !queryEvent.attendantId.isNullOrEmpty() - private fun createASubjectActionFromRecordCreation(enrolmentRecordCreation: EnrolmentRecordCreationInMove?): Write? = + private fun createASubjectActionFromRecordCreation(enrolmentRecordCreation: EnrolmentRecordCreationInMove?): Creation? = enrolmentRecordCreation?.let { val subject = subjectFactory.buildSubjectFromMovePayload(it) if (subject.fingerprintSamples.isNotEmpty() || subject.faceSamples.isNotEmpty()) { - Write(subject) + Creation(subject) } else { null } diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt index e35fea7312..ce832d5650 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt @@ -10,8 +10,8 @@ import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.sync.ConfigManager import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository import com.simprints.infra.enrolment.records.repository.domain.models.Subject +import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Creation import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Deletion -import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction.Write import com.simprints.infra.events.EventRepository import com.simprints.infra.events.event.domain.models.downsync.EventDownSyncRequestEvent import com.simprints.infra.events.event.domain.models.scope.EventScope @@ -265,7 +265,7 @@ class EventDownSyncTaskTest { coVerify { enrolmentRecordRepository.performActions( listOf( - Write( + Creation( subjectFactory.buildSubjectFromCreationPayload( event.payload, ), @@ -344,7 +344,7 @@ class EventDownSyncTaskTest { enrolmentRecordRepository.performActions( listOf( Deletion(eventToMoveToModule2.payload.enrolmentRecordDeletion.subjectId), - Write(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), + Creation(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), ), project, ) @@ -372,7 +372,7 @@ class EventDownSyncTaskTest { enrolmentRecordRepository.performActions( listOf( Deletion(eventToMoveToModule2.payload.enrolmentRecordDeletion.subjectId), - Write(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), + Creation(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), ), project, ) @@ -422,7 +422,7 @@ class EventDownSyncTaskTest { enrolmentRecordRepository.performActions( listOf( Deletion(eventToMoveToModule2.payload.enrolmentRecordDeletion.subjectId), - Write(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), + Creation(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), ), project, ) @@ -450,7 +450,7 @@ class EventDownSyncTaskTest { enrolmentRecordRepository.performActions( listOf( Deletion(eventToMoveToModule2.payload.enrolmentRecordDeletion.subjectId), - Write(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), + Creation(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), ), project, ) @@ -483,7 +483,7 @@ class EventDownSyncTaskTest { enrolmentRecordRepository.performActions( listOf( Deletion(eventToMoveToAttendant2.payload.enrolmentRecordDeletion.subjectId), - Write(subjectFactory.buildSubjectFromMovePayload(eventToMoveToAttendant2.payload.enrolmentRecordCreation)), + Creation(subjectFactory.buildSubjectFromMovePayload(eventToMoveToAttendant2.payload.enrolmentRecordCreation)), ), project, ) @@ -501,7 +501,7 @@ class EventDownSyncTaskTest { enrolmentRecordRepository.performActions( listOf( Deletion(eventToMoveToModule2.payload.enrolmentRecordDeletion.subjectId), - Write(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), + Creation(subjectFactory.buildSubjectFromMovePayload(eventToMoveToModule2.payload.enrolmentRecordCreation)), ), project, ) @@ -529,7 +529,7 @@ class EventDownSyncTaskTest { coVerify { enrolmentRecordRepository.performActions( - withArg { actions -> actions.all { it is Write } }, + withArg { actions -> actions.all { it is Creation } }, any(), ) } From cb971dfd36c99768b919936317113dfb41c82b0a Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 12 Feb 2025 17:38:37 +0200 Subject: [PATCH 20/22] MS-871 Replace deterministic reference ID with random UUID --- .../capture/screens/FaceCaptureViewModel.kt | 4 +- .../screen/FingerprintCaptureViewModel.kt | 8 +--- .../core/domain/common/ReferenceIds.kt | 25 ----------- .../core/domain/common/ReferenceIdsTest.kt | 42 ------------------- 4 files changed, 4 insertions(+), 75 deletions(-) delete mode 100644 infra/core/src/main/java/com/simprints/core/domain/common/ReferenceIds.kt delete mode 100644 infra/core/src/test/java/com/simprints/core/domain/common/ReferenceIdsTest.kt diff --git a/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt b/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt index b4007b7cde..178a7b573f 100644 --- a/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt +++ b/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt @@ -6,7 +6,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.simprints.core.DeviceID -import com.simprints.core.domain.common.faceReferenceId import com.simprints.core.livedata.LiveDataEvent import com.simprints.core.livedata.LiveDataEventWithContent import com.simprints.core.livedata.send @@ -37,6 +36,7 @@ import kotlinx.coroutines.flow.last import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import java.util.UUID import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject @@ -185,7 +185,7 @@ internal class FaceCaptureViewModel @Inject constructor( ), ) } - val referenceId = items.mapNotNull { it.sample?.template }.faceReferenceId().orEmpty() + val referenceId = UUID.randomUUID().toString() eventReporter.addBiometricReferenceCreationEvents(referenceId, items.mapNotNull { it.captureEventId }) _finishFlowEvent.send(FaceCaptureResult(referenceId, items)) diff --git a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt index fdb00bba65..e58c370849 100644 --- a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt +++ b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt @@ -6,7 +6,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.simprints.core.ExternalScope -import com.simprints.core.domain.common.fingerprintReferenceId import com.simprints.core.domain.fingerprint.IFingerIdentifier import com.simprints.core.livedata.LiveDataEvent import com.simprints.core.livedata.LiveDataEventWithContent @@ -63,6 +62,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import java.util.UUID import javax.inject.Inject import kotlin.math.min @@ -672,11 +672,7 @@ internal class FingerprintCaptureViewModel @Inject constructor( ), ) } - val biometricReferenceId = resultItems - .mapNotNull { it.sample } - .map { it.templateQualityScore to it.template } - .fingerprintReferenceId() - .orEmpty() + val biometricReferenceId = UUID.randomUUID().toString() addBiometricReferenceCreatedEvents(biometricReferenceId, resultItems.mapNotNull { it.captureEventId }) _finishWithFingerprints.send(FingerprintCaptureResult(biometricReferenceId, resultItems)) diff --git a/infra/core/src/main/java/com/simprints/core/domain/common/ReferenceIds.kt b/infra/core/src/main/java/com/simprints/core/domain/common/ReferenceIds.kt deleted file mode 100644 index bb8bbbe749..0000000000 --- a/infra/core/src/main/java/com/simprints/core/domain/common/ReferenceIds.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.simprints.core.domain.common - -import java.util.UUID - -/** - * Generate UUID based on the provided face templates. - */ -fun List.faceReferenceId(): String? = if (isNotEmpty()) { - sortedBy { it.contentHashCode() } - .fold(byteArrayOf()) { acc, template -> acc + template } - .let { UUID.nameUUIDFromBytes(it).toString() } -} else { - null -} - -/** - * Generate UUID based on the provided fingerprint templates and template quality scores. - */ -fun List>.fingerprintReferenceId(): String? = if (isNotEmpty()) { - sortedBy { it.first } - .fold(byteArrayOf()) { acc, sample -> acc + sample.second } - .let { UUID.nameUUIDFromBytes(it).toString() } -} else { - null -} diff --git a/infra/core/src/test/java/com/simprints/core/domain/common/ReferenceIdsTest.kt b/infra/core/src/test/java/com/simprints/core/domain/common/ReferenceIdsTest.kt deleted file mode 100644 index ea2b7e6841..0000000000 --- a/infra/core/src/test/java/com/simprints/core/domain/common/ReferenceIdsTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.simprints.core.domain.common - -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import java.util.UUID -import kotlin.collections.listOf - -class ReferenceIdsTest { - @Test - fun `face reference ID from arrays calculated from correctly`() { - val templates = listOf( - byteArrayOf(2), - byteArrayOf(1), - byteArrayOf(3), - ) - val expected = UUID.nameUUIDFromBytes(byteArrayOf(1, 2, 3)).toString() - - assertThat(templates.faceReferenceId()).isEqualTo(expected) - } - - @Test - fun `face reference ID returns null for empty list`() { - assertThat(listOf().faceReferenceId()).isNull() - } - - @Test - fun `fingerprint reference ID from arrays calculated from correctly`() { - val samples = listOf( - 3 to byteArrayOf(31, 32), - 2 to byteArrayOf(21, 22), - 1 to byteArrayOf(1, 2), - ) - val expected = UUID.nameUUIDFromBytes(byteArrayOf(1, 2, 21, 22, 31, 32)).toString() - - assertThat(samples.fingerprintReferenceId()).isEqualTo(expected) - } - - @Test - fun `fingerprint reference ID returns null for empty list`() { - assertThat(listOf>().fingerprintReferenceId()).isNull() - } -} From ec02b36d857a81d3062e12c29e4b2db4a3601d04 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 3 Mar 2025 09:46:18 +0200 Subject: [PATCH 21/22] MS-871 Fix naming and messaging in person creation references --- .../screen/EnrolLastBiometricViewModelTest.kt | 15 +++++++-------- .../usecases/response/EnrolSubjectUseCaseTest.kt | 10 +++++----- .../capture/screen/FingerprintCaptureViewModel.kt | 6 +++--- ... AddBiometricReferenceCreationEventUseCase.kt} | 2 +- .../screen/FingerprintCaptureViewModelTest.kt | 6 +++--- ...BiometricReferenceCreationEventUseCaseTest.kt} | 6 +++--- .../domain/validators/EnrolmentEventValidator.kt | 2 +- .../validators/EnrolmentEventValidatorTest.kt | 2 +- 8 files changed, 24 insertions(+), 25 deletions(-) rename fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/usecase/{AddBiometricReferenceCreationEventsUseCase.kt => AddBiometricReferenceCreationEventUseCase.kt} (93%) rename fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/usecase/{AddBiometricReferenceCreationEventsUseCaseTest.kt => AddBiometricReferenceCreationEventUseCaseTest.kt} (92%) diff --git a/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt b/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt index 083269c4bb..1ff2a99e34 100644 --- a/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt +++ b/feature/enrol-last-biometric/src/test/java/com/simprints/feature/enrollast/screen/EnrolLastBiometricViewModelTest.kt @@ -214,17 +214,16 @@ internal class EnrolLastBiometricViewModelTest { } @Test - fun `Uses all PersonCreationEvents for Enrolment event`() = runTest { - val personCreationEvent1 = mockk { - every { id } returns "personCreationEventId1" + fun `Uses all BiometricReferenceCreationEvent for Enrolment event`() = runTest { + val biometricReferenceCreationEvent1 = mockk { + every { id } returns "biometricReferenceCreationEventId1" every { payload } returns mockk { every { createdAt } returns Timestamp(1) every { id } returns "referenceId1" } } - val personCreationId2 = "personCreationEventId2" - val personCreationEvent2 = mockk { - every { id } returns personCreationId2 + val biometricReferenceCreationEvent2 = mockk { + every { id } returns "biometricReferenceCreationEventId2" every { payload } returns mockk { every { createdAt } returns Timestamp(2) every { id } returns "referenceId2" @@ -232,8 +231,8 @@ internal class EnrolLastBiometricViewModelTest { } coEvery { eventRepository.getEventsInCurrentSession() } returns listOf( - personCreationEvent2, - personCreationEvent1, + biometricReferenceCreationEvent2, + biometricReferenceCreationEvent1, ) viewModel.enrolBiometric(createParams(listOf())) diff --git a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt index eec3751e28..29648d3742 100644 --- a/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt +++ b/feature/orchestrator/src/test/java/com/simprints/feature/orchestrator/usecases/response/EnrolSubjectUseCaseTest.kt @@ -107,22 +107,22 @@ class EnrolSubjectUseCaseTest { } @Test - fun `Uses latest all BiometricReferenceCreationEvent`() = runTest { - val personCreationEvent1 = mockk { + fun `Uses all BiometricReferenceCreationEvent`() = runTest { + val biometricReferenceCreationEvent1 = mockk { every { payload } returns mockk { every { createdAt } returns Timestamp(1) every { id } returns "referenceId1" } } - val personCreationEvent2 = mockk { + val biometricReferenceCreationEvent2 = mockk { every { payload } returns mockk { every { createdAt } returns Timestamp(2) every { id } returns "referenceId2" } } coEvery { eventRepository.getEventsInCurrentSession() } returns listOf( - personCreationEvent2, - personCreationEvent1, + biometricReferenceCreationEvent2, + biometricReferenceCreationEvent1, ) useCase.invoke( diff --git a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt index e58c370849..daa3ed497e 100644 --- a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt +++ b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt @@ -24,7 +24,7 @@ import com.simprints.fingerprint.capture.state.CollectFingerprintsState import com.simprints.fingerprint.capture.state.FingerState import com.simprints.fingerprint.capture.state.LiveFeedbackState import com.simprints.fingerprint.capture.state.ScanResult -import com.simprints.fingerprint.capture.usecase.AddBiometricReferenceCreationEventsUseCase +import com.simprints.fingerprint.capture.usecase.AddBiometricReferenceCreationEventUseCase import com.simprints.fingerprint.capture.usecase.AddCaptureEventsUseCase import com.simprints.fingerprint.capture.usecase.GetNextFingerToAddUseCase import com.simprints.fingerprint.capture.usecase.GetStartStateUseCase @@ -76,7 +76,7 @@ internal class FingerprintCaptureViewModel @Inject constructor( private val getNextFingerToAdd: GetNextFingerToAddUseCase, private val getStartState: GetStartStateUseCase, private val addCaptureEvents: AddCaptureEventsUseCase, - private val addBiometricReferenceCreatedEvents: AddBiometricReferenceCreationEventsUseCase, + private val addBiometricReferenceCreationEvents: AddBiometricReferenceCreationEventUseCase, private val tracker: FingerprintScanningStatusTracker, private val isNoFingerDetectedLimitReachedUseCase: IsNoFingerDetectedLimitReachedUseCase, @ExternalScope private val externalScope: CoroutineScope, @@ -673,7 +673,7 @@ internal class FingerprintCaptureViewModel @Inject constructor( ) } val biometricReferenceId = UUID.randomUUID().toString() - addBiometricReferenceCreatedEvents(biometricReferenceId, resultItems.mapNotNull { it.captureEventId }) + addBiometricReferenceCreationEvents(biometricReferenceId, resultItems.mapNotNull { it.captureEventId }) _finishWithFingerprints.send(FingerprintCaptureResult(biometricReferenceId, resultItems)) } diff --git a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventsUseCase.kt b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventUseCase.kt similarity index 93% rename from fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventsUseCase.kt rename to fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventUseCase.kt index cee0a30b6a..57ad82ef14 100644 --- a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventsUseCase.kt +++ b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventUseCase.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import javax.inject.Inject -internal class AddBiometricReferenceCreationEventsUseCase @Inject constructor( +internal class AddBiometricReferenceCreationEventUseCase @Inject constructor( private val timeHelper: TimeHelper, private val eventRepository: SessionEventRepository, @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, diff --git a/fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModelTest.kt b/fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModelTest.kt index 8fdbf9f4f7..75dd864814 100644 --- a/fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModelTest.kt +++ b/fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModelTest.kt @@ -16,7 +16,7 @@ import com.simprints.fingerprint.capture.state.CollectFingerprintsState import com.simprints.fingerprint.capture.state.FingerState import com.simprints.fingerprint.capture.state.LiveFeedbackState import com.simprints.fingerprint.capture.state.ScanResult -import com.simprints.fingerprint.capture.usecase.AddBiometricReferenceCreationEventsUseCase +import com.simprints.fingerprint.capture.usecase.AddBiometricReferenceCreationEventUseCase import com.simprints.fingerprint.capture.usecase.AddCaptureEventsUseCase import com.simprints.fingerprint.capture.usecase.GetNextFingerToAddUseCase import com.simprints.fingerprint.capture.usecase.GetStartStateUseCase @@ -105,7 +105,7 @@ class FingerprintCaptureViewModelTest { private lateinit var addCaptureEventsUseCase: AddCaptureEventsUseCase @MockK - private lateinit var addBiometricReferenceCreatedEvents: AddBiometricReferenceCreationEventsUseCase + private lateinit var addBiometricReferenceCreatedEvents: AddBiometricReferenceCreationEventUseCase @MockK private lateinit var isNoFingerDetectedLimitReachedUseCase: IsNoFingerDetectedLimitReachedUseCase @@ -157,7 +157,7 @@ class FingerprintCaptureViewModelTest { getNextFingerToAdd = getNextFingerToAddUseCase, getStartState = getStartStateUseCase, addCaptureEvents = addCaptureEventsUseCase, - addBiometricReferenceCreatedEvents = addBiometricReferenceCreatedEvents, + addBiometricReferenceCreationEvents = addBiometricReferenceCreatedEvents, tracker = tracker, isNoFingerDetectedLimitReachedUseCase = isNoFingerDetectedLimitReachedUseCase, externalScope = CoroutineScope(testCoroutineRule.testCoroutineDispatcher), diff --git a/fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventsUseCaseTest.kt b/fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventUseCaseTest.kt similarity index 92% rename from fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventsUseCaseTest.kt rename to fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventUseCaseTest.kt index 6e44237307..27e3671d60 100644 --- a/fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventsUseCaseTest.kt +++ b/fingerprint/capture/src/test/java/com/simprints/fingerprint/capture/usecase/AddBiometricReferenceCreationEventUseCaseTest.kt @@ -15,7 +15,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test -internal class AddBiometricReferenceCreationEventsUseCaseTest { +internal class AddBiometricReferenceCreationEventUseCaseTest { @get:Rule val testCoroutineRule = TestCoroutineRule() @@ -25,7 +25,7 @@ internal class AddBiometricReferenceCreationEventsUseCaseTest { @MockK lateinit var eventRepo: SessionEventRepository - private lateinit var useCase: AddBiometricReferenceCreationEventsUseCase + private lateinit var useCase: AddBiometricReferenceCreationEventUseCase @Before fun setUp() { @@ -33,7 +33,7 @@ internal class AddBiometricReferenceCreationEventsUseCaseTest { coJustRun { eventRepo.addOrUpdateEvent(any()) } - useCase = AddBiometricReferenceCreationEventsUseCase( + useCase = AddBiometricReferenceCreationEventUseCase( timeHelper, eventRepo, CoroutineScope(testCoroutineRule.testCoroutineDispatcher), diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidator.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidator.kt index 0ec947a576..38c8cabfbf 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidator.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidator.kt @@ -26,7 +26,7 @@ internal class EnrolmentEventValidator : EventValidator { } if (!hasBiometricReference) { - throw EnrolmentEventValidatorException("Missing person creation event") + throw EnrolmentEventValidatorException("Missing biometric reference creation event") } } } diff --git a/infra/events/src/test/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidatorTest.kt b/infra/events/src/test/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidatorTest.kt index 0f3b5df45f..6afc0075b7 100644 --- a/infra/events/src/test/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidatorTest.kt +++ b/infra/events/src/test/java/com/simprints/infra/events/event/domain/validators/EnrolmentEventValidatorTest.kt @@ -21,7 +21,7 @@ internal class EnrolmentEventValidatorTest { } @Test - fun validate_shouldValidateIfBiometricCaptureAndPersonCreationIsPresent() { + fun validate_shouldValidateIfBiometricCaptureAndBiometricCreationIsPresent() { val currentEvents = listOf(createFaceCaptureEvent(), createPersonCreationEvent()) validator.run { validate(currentEvents, createBiometricReferenceCreationEvent()) } } From ad779e273cfffc1bfc955817fcca3c678cddf022 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Mon, 3 Mar 2025 14:29:30 +0200 Subject: [PATCH 22/22] MS-871 Use all provided biometric references to create subject --- .../infra/eventsync/sync/down/tasks/SubjectFactory.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt index e33619908a..97a52fad1a 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/sync/down/tasks/SubjectFactory.kt @@ -122,8 +122,8 @@ class SubjectFactory @Inject constructor( fun extractFingerprintSamplesFromBiometricReferences(biometricReferences: List?) = biometricReferences ?.filterIsInstance() - ?.firstOrNull() - ?.let { reference -> reference.templates.map { buildFingerprintSample(it, reference.format, reference.id) } } + ?.map { reference -> reference.templates.map { buildFingerprintSample(it, reference.format, reference.id) } } + ?.flatten() ?: emptyList() private fun buildFingerprintSample( @@ -140,8 +140,8 @@ class SubjectFactory @Inject constructor( fun extractFaceSamplesFromBiometricReferences(biometricReferences: List?) = biometricReferences ?.filterIsInstance() - ?.firstOrNull() - ?.let { reference -> reference.templates.map { buildFaceSample(it, reference.format, reference.id) } } + ?.map { reference -> reference.templates.map { buildFaceSample(it, reference.format, reference.id) } } + ?.flatten() ?: emptyList() private fun buildFaceSample(