From 84a0044edb7fdaed1dbe43e3522e4da9e23f7673 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 29 Jul 2025 12:22:48 +0300 Subject: [PATCH 01/20] =?UTF-8?q?WIP=20[CORE-3421]=20first=20steps=20of=20?= =?UTF-8?q?adding=20the=20external=20credentials=20support=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/config/store/models/Project.kt | 1 + .../store/models/ProjectConfiguration.kt | 1 + .../models/SearchAndVerifyConfiguration.kt | 7 ++++ .../remote/models/ApiProjectConfiguration.kt | 1 + .../models/ApiSearchAndVerifyConfiguration.kt | 27 +++++++++++++++ .../externalcredential/ExternalCredential.kt | 14 ++++++++ .../ExternalCredentialType.kt | 5 +++ .../ExternalCredentialRepository.kt | 8 +++++ .../repository/domain/models/Subject.kt | 2 ++ .../repository/domain/models/SubjectAction.kt | 2 ++ .../repository/domain/models/SubjectQuery.kt | 1 + .../RoomEnrolmentRecordLocalDataSource.kt | 4 +++ .../models/RoomExternalCredentialConverter.kt | 22 +++++++++++++ .../local/models/RoomSubjectConverter.kt | 1 + .../records/room/store/SubjectDao.kt | 12 +++++++ .../records/room/store/SubjectsDatabase.kt | 2 ++ .../room/store/models/DbBiometricTemplate.kt | 1 + .../room/store/models/DbExternalCredential.kt | 33 +++++++++++++++++++ .../room/store/models/SubjectBiometrics.kt | 6 ++++ .../models/SubjectExternalCredentials.kt | 14 ++++++++ .../ApiEnrolmentRecordCreationPayload.kt | 2 ++ .../ApiEnrolmentRecordUpdatePayload.kt | 4 +++ .../models/subject/ApiExternalCredential.kt | 18 ++++++++++ .../subject/EnrolmentRecordCreationEvent.kt | 2 ++ .../subject/EnrolmentRecordUpdateEvent.kt | 10 ++++-- 25 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 infra/config-store/src/main/java/com/simprints/infra/config/store/models/SearchAndVerifyConfiguration.kt create mode 100644 infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSearchAndVerifyConfiguration.kt create mode 100644 infra/core/src/main/java/com/simprints/core/domain/externalcredential/ExternalCredential.kt create mode 100644 infra/core/src/main/java/com/simprints/core/domain/externalcredential/ExternalCredentialType.kt create mode 100644 infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/ExternalCredentialRepository.kt create mode 100644 infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RoomExternalCredentialConverter.kt create mode 100644 infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/DbExternalCredential.kt create mode 100644 infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectExternalCredentials.kt create mode 100644 infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiExternalCredential.kt diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/Project.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/Project.kt index f9a97c7a52..acb5c4c235 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/Project.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/Project.kt @@ -14,5 +14,6 @@ data class Project( enum class TokenKeyType { AttendantId, ModuleId, + ExternalCredential, Unknown, } diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ProjectConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ProjectConfiguration.kt index de6e8d9aaf..39ea770b3f 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ProjectConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ProjectConfiguration.kt @@ -10,6 +10,7 @@ data class ProjectConfiguration( val consent: ConsentConfiguration, val identification: IdentificationConfiguration, val synchronization: SynchronizationConfiguration, + val searchAndVerify: SearchAndVerifyConfiguration?, val custom: Map?, ) diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/SearchAndVerifyConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/SearchAndVerifyConfiguration.kt new file mode 100644 index 0000000000..ee29b0528f --- /dev/null +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/SearchAndVerifyConfiguration.kt @@ -0,0 +1,7 @@ +package com.simprints.infra.config.store.models + +import com.simprints.core.domain.externalcredential.ExternalCredentialType + +data class SearchAndVerifyConfiguration( + val allowedExternalCredentials: List +) diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiProjectConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiProjectConfiguration.kt index e4b589bcd7..c782b25f79 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiProjectConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiProjectConfiguration.kt @@ -14,6 +14,7 @@ internal data class ApiProjectConfiguration( val consent: ApiConsentConfiguration, val identification: ApiIdentificationConfiguration, val synchronization: ApiSynchronizationConfiguration, + val searchAndVerify: ApiSearchAndVerifyConfiguration?, val custom: Map?, ) { fun toDomain(): ProjectConfiguration = ProjectConfiguration( diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSearchAndVerifyConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSearchAndVerifyConfiguration.kt new file mode 100644 index 0000000000..8b3eb3ca29 --- /dev/null +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSearchAndVerifyConfiguration.kt @@ -0,0 +1,27 @@ +package com.simprints.infra.config.store.remote.models + +import androidx.annotation.Keep +import com.simprints.core.domain.tokenization.asTokenizableEncrypted +import com.simprints.infra.config.store.models.DownSynchronizationConfiguration +import com.simprints.infra.config.store.models.DownSynchronizationConfiguration.Companion.DEFAULT_DOWN_SYNC_MAX_AGE +import com.simprints.infra.config.store.models.Frequency +import com.simprints.infra.config.store.models.SampleSynchronizationConfiguration +import com.simprints.infra.config.store.models.SynchronizationConfiguration +import com.simprints.infra.config.store.models.UpSynchronizationConfiguration + +@Keep +internal data class ApiSearchAndVerifyConfiguration( + val allowedExternalCredentials: List +) + +enum class ApiExternalCredentialType { + NHISCard, GhanaIdCard, QRCode +} + +fun mapToString(a: ApiExternalCredentialType) { + return when(a) + { + ApiExternalCredentialType.NHISCard -> TODO() + ApiExternalCredentialType.GhanaIdCard -> TODO() + ApiExternalCredentialType.QRCode -> TODO() + }} diff --git a/infra/core/src/main/java/com/simprints/core/domain/externalcredential/ExternalCredential.kt b/infra/core/src/main/java/com/simprints/core/domain/externalcredential/ExternalCredential.kt new file mode 100644 index 0000000000..478508ce21 --- /dev/null +++ b/infra/core/src/main/java/com/simprints/core/domain/externalcredential/ExternalCredential.kt @@ -0,0 +1,14 @@ +package com.simprints.core.domain.externalcredential + +import android.os.Parcelable +import com.simprints.core.ExcludedFromGeneratedTestCoverageReports +import com.simprints.core.domain.tokenization.TokenizableString +import kotlinx.parcelize.Parcelize + +@Parcelize +@ExcludedFromGeneratedTestCoverageReports("Data class with generated code") +data class ExternalCredential( + val value: TokenizableString.Tokenized, + val subjectId: String, + val type: ExternalCredentialType, +) : Parcelable diff --git a/infra/core/src/main/java/com/simprints/core/domain/externalcredential/ExternalCredentialType.kt b/infra/core/src/main/java/com/simprints/core/domain/externalcredential/ExternalCredentialType.kt new file mode 100644 index 0000000000..1481ce4af3 --- /dev/null +++ b/infra/core/src/main/java/com/simprints/core/domain/externalcredential/ExternalCredentialType.kt @@ -0,0 +1,5 @@ +package com.simprints.core.domain.externalcredential + +enum class ExternalCredentialType { + NHISCard, GhanaIdCard, QRCode +} diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/ExternalCredentialRepository.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/ExternalCredentialRepository.kt new file mode 100644 index 0000000000..aac8a37e6a --- /dev/null +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/ExternalCredentialRepository.kt @@ -0,0 +1,8 @@ +package com.simprints.infra.enrolment.records.repository + +import javax.inject.Inject + +internal class ExternalCredentialRepository @Inject constructor( + +){ +} 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 23d307709b..3cc6060579 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 @@ -1,6 +1,7 @@ package com.simprints.infra.enrolment.records.repository.domain.models import android.os.Parcelable +import com.simprints.core.domain.externalcredential.ExternalCredential import com.simprints.core.domain.face.FaceSample import com.simprints.core.domain.fingerprint.FingerprintSample import com.simprints.core.domain.tokenization.TokenizableString @@ -17,4 +18,5 @@ data class Subject( val updatedAt: Date? = null, var fingerprintSamples: List = emptyList(), var faceSamples: List = emptyList(), + var externalCredentials: List = emptyList(), ) : Parcelable 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 421a6f11c6..7953eac800 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,7 @@ package com.simprints.infra.enrolment.records.repository.domain.models import androidx.annotation.Keep +import com.simprints.core.domain.externalcredential.ExternalCredential import com.simprints.core.domain.face.FaceSample import com.simprints.core.domain.fingerprint.FingerprintSample @@ -14,6 +15,7 @@ sealed class SubjectAction { val subjectId: String, val faceSamplesToAdd: List, val fingerprintSamplesToAdd: List, + val externalCredentialsToAdd: List, val referenceIdsToRemove: List, ) : SubjectAction() diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectQuery.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectQuery.kt index 73f3af0d43..2151ecb31e 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectQuery.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/domain/models/SubjectQuery.kt @@ -17,4 +17,5 @@ data class SubjectQuery( val sort: Boolean = false, val afterSubjectId: String? = null, val metadata: String? = null, + val externalCredential: String? = null, ) : StepParams diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSource.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSource.kt index 61aed68169..505d9d323f 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSource.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSource.kt @@ -289,6 +289,10 @@ internal class RoomEnrolmentRecordLocalDataSource @Inject constructor( val dbFaces = samples.map { it.toRoomDb(subject.subjectId) } subjectDao.insertBiometricSamples(dbFaces) } + subject.externalCredentials.takeIf { it.isNotEmpty() }?.let { credentials -> + val dbExternalCredentials = credentials.map { it.toRoomDb() } + subjectDao.insertExternalCredentials(dbExternalCredentials) + } } private suspend fun updateSubject(action: SubjectAction.Update) { diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RoomExternalCredentialConverter.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RoomExternalCredentialConverter.kt new file mode 100644 index 0000000000..d43b2fe5af --- /dev/null +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RoomExternalCredentialConverter.kt @@ -0,0 +1,22 @@ +package com.simprints.infra.enrolment.records.repository.local.models + +import com.simprints.core.domain.externalcredential.ExternalCredential +import com.simprints.core.domain.externalcredential.ExternalCredentialType +import com.simprints.core.domain.face.FaceSample +import com.simprints.core.domain.fingerprint.FingerprintSample +import com.simprints.infra.enrolment.records.room.store.models.DbBiometricTemplate +import com.simprints.infra.enrolment.records.room.store.models.DbExternalCredential +import com.simprints.infra.enrolment.records.room.store.models.Modality + +internal fun DbExternalCredential.toDomain(): ExternalCredential = ExternalCredential( + value = value, + subjectId = subjectId, + type = ExternalCredentialType.valueOf(type) +) + +internal fun ExternalCredential.toRoomDb(): DbExternalCredential = DbExternalCredential( + value = value, + subjectId = subjectId, + type = type.name +) + diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RoomSubjectConverter.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RoomSubjectConverter.kt index 385e341830..456b54b7e6 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RoomSubjectConverter.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RoomSubjectConverter.kt @@ -15,6 +15,7 @@ internal fun SubjectBiometrics.toDomain() = Subject( updatedAt = subject.updatedAt?.toDate(), fingerprintSamples = biometricTemplates.filter { it.modality == Modality.FINGERPRINT.id }.map { it.toFingerprintSample() }, faceSamples = biometricTemplates.filter { it.modality == Modality.FACE.id }.map { it.toFaceSample() }, + externalCredentials = externalCredentials.map { it.toDomain() }, ) fun Long.toDate() = Date(this) diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt index d7836eb8cf..d682a67b2a 100644 --- a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt @@ -8,23 +8,32 @@ import androidx.room.Query import androidx.room.RawQuery import androidx.sqlite.db.SupportSQLiteQuery import com.simprints.infra.enrolment.records.room.store.models.DbBiometricTemplate +import com.simprints.infra.enrolment.records.room.store.models.DbExternalCredential import com.simprints.infra.enrolment.records.room.store.models.DbSubject import com.simprints.infra.enrolment.records.room.store.models.SubjectBiometrics +import com.simprints.infra.enrolment.records.room.store.models.SubjectExternalCredentials @Dao interface SubjectDao { + /*Remaining method*/ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertSubject(subject: DbSubject) @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertBiometricSamples(samples: List) + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertExternalCredentials(value: List) + @Query("DELETE FROM DbSubject WHERE subjectId = :subjectId") suspend fun deleteSubject(subjectId: String) @Query("DELETE FROM DbBiometricTemplate WHERE uuid = :uuid") suspend fun deleteBiometricSample(uuid: String) + @Query("DELETE FROM DbDbExternalCredential WHERE value = :value") + suspend fun deleteExternalCredential(value: String) + @RawQuery suspend fun deleteSubjects(query: SupportSQLiteQuery): Int @@ -34,6 +43,9 @@ interface SubjectDao { @RawQuery suspend fun loadSubjects(query: SupportSQLiteQuery): List + @Query("SELECT * FROM DbDbExternalCredential") + suspend fun getAllExternalCredentials(): List + @RawQuery suspend fun countSubjects(query: SupportSQLiteQuery): Int diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectsDatabase.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectsDatabase.kt index 16f004f62d..7b4e9f3641 100644 --- a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectsDatabase.kt +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectsDatabase.kt @@ -6,6 +6,7 @@ import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import com.simprints.infra.enrolment.records.room.store.models.DbBiometricTemplate +import com.simprints.infra.enrolment.records.room.store.models.DbExternalCredential import com.simprints.infra.enrolment.records.room.store.models.DbSubject import net.zetetic.database.sqlcipher.SupportOpenHelperFactory import javax.inject.Singleton @@ -15,6 +16,7 @@ import javax.inject.Singleton entities = [ DbSubject::class, DbBiometricTemplate::class, + DbExternalCredential::class, ], version = 1, exportSchema = true, diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/DbBiometricTemplate.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/DbBiometricTemplate.kt index 7590c579bd..91c0075666 100644 --- a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/DbBiometricTemplate.kt +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/DbBiometricTemplate.kt @@ -39,3 +39,4 @@ data class DbBiometricTemplate( const val FORMAT_COLUMN = "format" } } + diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/DbExternalCredential.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/DbExternalCredential.kt new file mode 100644 index 0000000000..7468c1dbc2 --- /dev/null +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/DbExternalCredential.kt @@ -0,0 +1,33 @@ +package com.simprints.infra.enrolment.records.room.store.models + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey +import com.simprints.infra.enrolment.records.room.store.models.DbExternalCredential.Companion.EXTERNAL_CREDENTIAL_TABLE_NAME +import com.simprints.infra.enrolment.records.room.store.models.DbExternalCredential.Companion.EXTERNAL_CREDENTIAL_VALUE_COLUMN +import com.simprints.infra.enrolment.records.room.store.models.DbSubject.Companion.SUBJECT_ID_COLUMN + +@Entity( + tableName = EXTERNAL_CREDENTIAL_TABLE_NAME, + primaryKeys = [EXTERNAL_CREDENTIAL_VALUE_COLUMN, SUBJECT_ID_COLUMN], + foreignKeys = [ + ForeignKey( + entity = DbSubject::class, + parentColumns = [SUBJECT_ID_COLUMN], + childColumns = [SUBJECT_ID_COLUMN], + onDelete = ForeignKey.CASCADE, + ) + ] +) +data class DbExternalCredential( + @ColumnInfo(name = EXTERNAL_CREDENTIAL_VALUE_COLUMN) + val value: String, + @ColumnInfo(name = SUBJECT_ID_COLUMN) + val subjectId: String, + val type: String, +) { + companion object { + const val EXTERNAL_CREDENTIAL_VALUE_COLUMN = "value" + const val EXTERNAL_CREDENTIAL_TABLE_NAME = "DbDbExternalCredential" + } +} diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectBiometrics.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectBiometrics.kt index 1ea2f722d2..d267894645 100644 --- a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectBiometrics.kt +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectBiometrics.kt @@ -11,4 +11,10 @@ data class SubjectBiometrics( entityColumn = SUBJECT_ID_COLUMN, ) val biometricTemplates: List, + @Relation( + parentColumn = SUBJECT_ID_COLUMN, + entityColumn = SUBJECT_ID_COLUMN, + ) + /** New field */ + val externalCredentials: List, ) diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectExternalCredentials.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectExternalCredentials.kt new file mode 100644 index 0000000000..f710dfa45a --- /dev/null +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectExternalCredentials.kt @@ -0,0 +1,14 @@ +package com.simprints.infra.enrolment.records.room.store.models + +import androidx.room.Embedded +import androidx.room.Relation +import com.simprints.infra.enrolment.records.room.store.models.DbSubject.Companion.SUBJECT_ID_COLUMN + +data class SubjectExternalCredentials( + @Embedded val subject: DbSubject, + @Relation( + parentColumn = SUBJECT_ID_COLUMN, + entityColumn = SUBJECT_ID_COLUMN, + ) + val externalCredentials: List, +) diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationPayload.kt index 94f749f810..3332ea9773 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationPayload.kt @@ -4,6 +4,7 @@ import androidx.annotation.Keep import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonInclude.Include import com.simprints.core.domain.tokenization.asTokenizableEncrypted +import com.simprints.infra.config.store.remote.models.ApiExternalCredentialType import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordCreationEvent import com.simprints.infra.eventsync.event.remote.models.subject.biometricref.ApiBiometricReference import com.simprints.infra.eventsync.event.remote.models.subject.biometricref.fromApiToDomain @@ -16,6 +17,7 @@ internal data class ApiEnrolmentRecordCreationPayload( val moduleId: String, val attendantId: String, val biometricReferences: List?, + val externalCredential: ApiExternalCredential?, ) : ApiEnrolmentRecordEventPayload(ApiEnrolmentRecordPayloadType.EnrolmentRecordCreation) internal fun ApiEnrolmentRecordCreationPayload.fromApiToDomain() = EnrolmentRecordCreationEvent.EnrolmentRecordCreationPayload( 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 57014fc4ef..414c9ab056 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 @@ -1,6 +1,7 @@ package com.simprints.infra.eventsync.event.remote.models.subject import androidx.annotation.Keep +import com.simprints.core.domain.externalcredential.ExternalCredential 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 @@ -10,10 +11,13 @@ internal data class ApiEnrolmentRecordUpdatePayload( val subjectId: String, val biometricReferencesAdded: List?, val biometricReferencesRemoved: List?, + val externalCredentialAdded: ApiExternalCredential?, ) : ApiEnrolmentRecordEventPayload(ApiEnrolmentRecordPayloadType.EnrolmentRecordUpdate) internal fun ApiEnrolmentRecordUpdatePayload.fromApiToDomain() = EnrolmentRecordUpdateEvent.EnrolmentRecordUpdatePayload( subjectId, biometricReferencesAdded?.map { it.fromApiToDomain() }.orEmpty(), biometricReferencesRemoved.orEmpty(), + externalCredentialAdded?.fromApiToDomain(subjectId), ) + diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiExternalCredential.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiExternalCredential.kt new file mode 100644 index 0000000000..e1890996e3 --- /dev/null +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiExternalCredential.kt @@ -0,0 +1,18 @@ +package com.simprints.infra.eventsync.event.remote.models.subject + +import com.simprints.core.domain.externalcredential.ExternalCredential +import com.simprints.core.domain.externalcredential.ExternalCredentialType +import com.simprints.core.domain.tokenization.asTokenizableEncrypted + +data class ApiExternalCredential( + val id: String, + val type: String, + val value: String, +) + + +internal fun ApiExternalCredential.fromApiToDomain(subjectId: String) = ExternalCredential( + value = value.asTokenizableEncrypted(), + subjectId = subjectId, + type = ExternalCredentialType.valueOf(type) +) 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 e583230b9b..d9375fd3a1 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,6 +1,7 @@ package com.simprints.infra.events.event.domain.models.subject import androidx.annotation.Keep +import com.simprints.core.domain.externalcredential.ExternalCredential import com.simprints.core.domain.face.FaceSample import com.simprints.core.domain.fingerprint.FingerprintSample import com.simprints.core.domain.tokenization.TokenizableString @@ -36,6 +37,7 @@ data class EnrolmentRecordCreationEvent( val moduleId: TokenizableString, val attendantId: TokenizableString, val biometricReferences: List, + val externalCredential: ExternalCredential? ) companion object { 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 index 4ca63c97f8..cd50c3ffd0 100644 --- 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 @@ -1,6 +1,7 @@ package com.simprints.infra.events.event.domain.models.subject import androidx.annotation.Keep +import com.simprints.core.domain.externalcredential.ExternalCredential import java.util.UUID @Keep @@ -12,12 +13,14 @@ data class EnrolmentRecordUpdateEvent( subjectId: String, biometricReferencesAdded: List, biometricReferencesRemoved: List, + externalCredentialAdded: ExternalCredential?, ) : this( UUID.randomUUID().toString(), EnrolmentRecordUpdatePayload( - subjectId, - biometricReferencesAdded, - biometricReferencesRemoved, + subjectId = subjectId, + biometricReferencesAdded = biometricReferencesAdded, + biometricReferencesRemoved = biometricReferencesRemoved, + externalCredentialAdded = externalCredentialAdded, ), ) @@ -26,5 +29,6 @@ data class EnrolmentRecordUpdateEvent( val subjectId: String, val biometricReferencesAdded: List, val biometricReferencesRemoved: List, + val externalCredentialAdded: ExternalCredential?, ) } From 9504059324b191098019a189b2147d8b04a5aa5b Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 4 Aug 2025 14:06:05 +0300 Subject: [PATCH 02/20] =?UTF-8?q?[WIP]=20Adding=20external=20credentials?= =?UTF-8?q?=20to=20the=20Realm=20DB=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/models/DbExternalCredential.kt | 21 +++++++++++++++++++ .../records/realm/store/models/DbSubject.kt | 1 + .../RealmEnrolmentRecordLocalDataSource.kt | 16 ++++++++++++-- .../models/RealmFingerprintSampleConverter.kt | 8 +++++++ 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbExternalCredential.kt diff --git a/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbExternalCredential.kt b/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbExternalCredential.kt new file mode 100644 index 0000000000..d076cca478 --- /dev/null +++ b/infra/enrolment-records/realm-store/src/main/java/com/simprints/infra/enrolment/records/realm/store/models/DbExternalCredential.kt @@ -0,0 +1,21 @@ +package com.simprints.infra.enrolment.records.realm.store.models + +import androidx.annotation.Keep +import com.simprints.core.ExcludedFromGeneratedTestCoverageReports +import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.annotations.PrimaryKey + +@Keep +@ExcludedFromGeneratedTestCoverageReports("Data model definition for Realm table") +class DbExternalCredential : RealmObject { + @PrimaryKey + var id: String = "" + get() = "$value$SEPARATOR$subjectId" + var value: String = "" + var subjectId: String = "" + var type: String = "" + + companion object { + const val SEPARATOR = "|" + } +} 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 612e8bb8a7..4824b59a16 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 @@ -22,6 +22,7 @@ class DbSubject : RealmObject { var fingerprintSamples: RealmList = realmListOf() var faceSamples: RealmList = realmListOf() + var externalCredentials: RealmList = realmListOf() var isAttendantIdTokenized: Boolean = false var isModuleIdTokenized: Boolean = false diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSource.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSource.kt index b3b2e3037d..1fe1969bc6 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSource.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSource.kt @@ -211,6 +211,12 @@ internal class RealmEnrolmentRecordLocalDataSource @Inject constructor( .filterNot { it.id in faceSampleIds } .takeIf { it.isNotEmpty() } ?.forEach { realm.delete(it) } + + val externalCredentialIds = newSubject.externalCredentials.map { it.id }.toSet() + dbSubject.externalCredentials + .filterNot { it.id in externalCredentialIds } + .takeIf { it.isNotEmpty() } + ?.forEach { realm.delete(it) } } realm.copyToRealm(newSubject, updatePolicy = UpdatePolicy.ALL) @@ -220,16 +226,22 @@ internal class RealmEnrolmentRecordLocalDataSource @Inject constructor( val dbSubject: DbSubject? = realm.findSubject(RealmUUID.from(action.subjectId)) if (dbSubject != null) { val referencesToDelete = action.referenceIdsToRemove.toSet() // to make lookup O(1) + val externalCredentialsToAdd = action.externalCredentialsToAdd.toSet() // to make lookup O(1) val faceSamplesMap = dbSubject.faceSamples.groupBy { it.referenceId in referencesToDelete } val fingerprintSamplesMap = dbSubject.fingerprintSamples.groupBy { it.referenceId in referencesToDelete } +// val externalCredentialsMap = +// dbSubject.externalCredentials.groupBy { it.value in externalCredentialsToAdd.map { it.value.value } } + + val allExternalCredentials = (dbSubject.externalCredentials + action.externalCredentialsToAdd.map { it.toRealmDb() }).distinctBy { it.id }.toSet() // Append new samples to the list of samples that remain after removing dbSubject.faceSamples = ( faceSamplesMap[false].orEmpty() + action.faceSamplesToAdd.map { it.toRealmDb() } - ).toRealmList() + ).toRealmList() dbSubject.fingerprintSamples = ( fingerprintSamplesMap[false].orEmpty() + action.fingerprintSamplesToAdd.map { it.toRealmDb() } - ).toRealmList() + ).toRealmList() + dbSubject.externalCredentials = allExternalCredentials.toRealmList() faceSamplesMap[true]?.forEach { realm.delete(it) } fingerprintSamplesMap[true]?.forEach { realm.delete(it) } diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RealmFingerprintSampleConverter.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RealmFingerprintSampleConverter.kt index 6b2be097ba..ebfab4e594 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RealmFingerprintSampleConverter.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RealmFingerprintSampleConverter.kt @@ -1,7 +1,9 @@ package com.simprints.infra.enrolment.records.repository.local.models +import com.simprints.core.domain.externalcredential.ExternalCredential import com.simprints.core.domain.fingerprint.FingerprintSample import com.simprints.infra.enrolment.records.realm.store.models.DbFingerprintSample as RealmFingerprintSample +import com.simprints.infra.enrolment.records.realm.store.models.DbExternalCredential as RealmExternalCredential internal fun RealmFingerprintSample.toDomain(): FingerprintSample = FingerprintSample( id = id, @@ -18,3 +20,9 @@ internal fun FingerprintSample.toRealmDb(): RealmFingerprintSample = RealmFinger sample.template = template sample.format = format } + +internal fun ExternalCredential.toRealmDb(): RealmExternalCredential = RealmExternalCredential().also { sample -> + sample.value = value.value + sample.subjectId = subjectId + sample.type = type.toString() +} From f279637b81a7dfb431271e0c1f2a321b0c1b76aa Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 10 Aug 2025 17:37:12 +0300 Subject: [PATCH 03/20] =?UTF-8?q?[CORE-3421]=20Adding=20external=20credent?= =?UTF-8?q?ial=20support=20on=20Domain,=20DB=20(Realm,=20Room)=20and=20API?= =?UTF-8?q?=20levels=20=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...EnrolmentCreationEventForSubjectUseCase.kt | 12 +++++---- .../store/local/ConfigLocalDataSourceImpl.kt | 1 + .../migrations/models/OldProjectConfig.kt | 1 + .../local/models/ExternalCredentialType.kt | 18 +++++++++++++ .../models/MutliFactorIdConfiguration.kt | 12 +++++++++ .../local/models/ProjectConfiguration.kt | 22 ++++++++------- ...ation.kt => MultiFactorIdConfiguration.kt} | 2 +- .../store/models/ProjectConfiguration.kt | 2 +- .../models/ApiMultiFactorIdConfiguration.kt | 25 +++++++++++++++++ .../remote/models/ApiProjectConfiguration.kt | 23 ++++++++-------- .../models/ApiSearchAndVerifyConfiguration.kt | 27 ------------------- .../src/main/proto/project_config.proto | 12 +++++++++ .../records/realm/store/config/RealmConfig.kt | 4 ++- .../RealmEnrolmentRecordLocalDataSource.kt | 4 --- .../RoomEnrolmentRecordLocalDataSource.kt | 3 +++ .../models/RoomExternalCredentialConverter.kt | 9 +++---- .../1.json | 2 +- .../remote/models/ApiEnrolmentPayloadV2.kt | 1 + .../remote/models/ApiEnrolmentPayloadV4.kt | 1 + .../models/callout/ApiCalloutPayloadV2.kt | 1 + .../models/callout/ApiCalloutPayloadV3.kt | 1 + .../ApiEnrolmentRecordCreationPayload.kt | 11 ++++---- .../sync/down/tasks/EventDownSyncTask.kt | 1 + ...nrolmentRecordCreationEventDeserializer.kt | 12 +++++---- .../subject/EnrolmentRecordCreationEvent.kt | 12 +++++---- 25 files changed, 137 insertions(+), 82 deletions(-) create mode 100644 infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/ExternalCredentialType.kt create mode 100644 infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/MutliFactorIdConfiguration.kt rename infra/config-store/src/main/java/com/simprints/infra/config/store/models/{SearchAndVerifyConfiguration.kt => MultiFactorIdConfiguration.kt} (82%) create mode 100644 infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiMultiFactorIdConfiguration.kt delete mode 100644 infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSearchAndVerifyConfiguration.kt diff --git a/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/GetEnrolmentCreationEventForSubjectUseCase.kt b/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/GetEnrolmentCreationEventForSubjectUseCase.kt index 6661aa5441..40359a9ac5 100644 --- a/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/GetEnrolmentCreationEventForSubjectUseCase.kt +++ b/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/GetEnrolmentCreationEventForSubjectUseCase.kt @@ -47,11 +47,13 @@ internal class GetEnrolmentCreationEventForSubjectUseCase @Inject constructor( } private fun Subject.fromSubjectToEnrolmentCreationEvent() = EnrolmentRecordCreationEvent( - subjectId, - projectId, - moduleId, - attendantId, - EnrolmentRecordCreationEvent.buildBiometricReferences(fingerprintSamples, faceSamples, encoder), + subjectId = subjectId, + projectId = projectId, + moduleId = moduleId, + attendantId = attendantId, + biometricReferences = EnrolmentRecordCreationEvent.buildBiometricReferences(fingerprintSamples, faceSamples, encoder), + // TODO [CORE-3421] Review if EnrolmentRecordCreationEvent should contain List of external credentials, as it currently doesn't make sense + externalCredential = externalCredentials.firstOrNull() ) companion object { diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImpl.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImpl.kt index 633ab8e1f3..69ebd618fa 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImpl.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImpl.kt @@ -238,6 +238,7 @@ internal class ConfigLocalDataSourceImpl @Inject constructor( ), ), custom = null, + multifactorId = null ).toProto() val defaultDeviceConfiguration: ProtoDeviceConfiguration = DeviceConfiguration( language = "", diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt index 75352715fc..38d5957162 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/migrations/models/OldProjectConfig.kt @@ -75,6 +75,7 @@ internal data class OldProjectConfig( consent = consentConfiguration(), identification = identificationConfiguration(), synchronization = synchronizationConfiguration(), + multifactorId = null, custom = null, ) diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/ExternalCredentialType.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/ExternalCredentialType.kt new file mode 100644 index 0000000000..c2f20a6014 --- /dev/null +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/ExternalCredentialType.kt @@ -0,0 +1,18 @@ +package com.simprints.infra.config.store.local.models + +import com.simprints.core.domain.externalcredential.ExternalCredentialType +import com.simprints.infra.config.store.exceptions.InvalidProtobufEnumException + +internal fun ExternalCredentialType.toProto(): ProtoExternalCredentialType = when(this){ + ExternalCredentialType.NHISCard -> ProtoExternalCredentialType.NHIS_CARD + ExternalCredentialType.GhanaIdCard -> ProtoExternalCredentialType.GHANA_ID_CARD + ExternalCredentialType.QRCode -> ProtoExternalCredentialType.QR_CODE +} + +internal fun ProtoExternalCredentialType.toDomain(): ExternalCredentialType = when(this){ + ProtoExternalCredentialType.UNRECOGNIZED, + ProtoExternalCredentialType.EXTERNAL_CREDENTIAL_TYPE_UNSPECIFIED -> throw InvalidProtobufEnumException("invalid External credential $name") + ProtoExternalCredentialType.NHIS_CARD -> ExternalCredentialType.NHISCard + ProtoExternalCredentialType.GHANA_ID_CARD -> ExternalCredentialType.GhanaIdCard + ProtoExternalCredentialType.QR_CODE -> ExternalCredentialType.QRCode +} diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/MutliFactorIdConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/MutliFactorIdConfiguration.kt new file mode 100644 index 0000000000..100fa4afe0 --- /dev/null +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/MutliFactorIdConfiguration.kt @@ -0,0 +1,12 @@ +package com.simprints.infra.config.store.local.models + +import com.simprints.infra.config.store.models.MultiFactorIdConfiguration + +internal fun MultiFactorIdConfiguration.toProto(): ProtoMultiFactorIdConfiguration = ProtoMultiFactorIdConfiguration + .newBuilder() + .addAllAllowedExternalCredentials(allowedExternalCredentials.map { it.toProto() }) + .build() + +internal fun ProtoMultiFactorIdConfiguration.toDomain(): MultiFactorIdConfiguration = MultiFactorIdConfiguration( + allowedExternalCredentials = allowedExternalCredentialsList.map { it.toDomain() } +) diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/ProjectConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/ProjectConfiguration.kt index 9579957ae7..93acadfa1a 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/ProjectConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/local/models/ProjectConfiguration.kt @@ -15,6 +15,7 @@ internal fun ProjectConfiguration.toProto(): ProtoProjectConfiguration = ProtoPr .also { if (face != null) it.face = face.toProto() if (fingerprint != null) it.fingerprint = fingerprint.toProto() + if (multifactorId != null) it.multiFactorId = multifactorId.toProto() }.also { if (custom != null) { try { @@ -28,16 +29,17 @@ internal fun ProjectConfiguration.toProto(): ProtoProjectConfiguration = ProtoPr }.build() internal fun ProtoProjectConfiguration.toDomain(): ProjectConfiguration = ProjectConfiguration( - id, - projectId, - updatedAt, - general.toDomain(), - hasFace().let { if (it) face.toDomain() else null }, - hasFingerprint().let { if (it) fingerprint.toDomain() else null }, - consent.toDomain(), - identification.toDomain(), - synchronization.toDomain(), - customJson?.takeIf { it.isNotBlank() }?.let { + id = id, + projectId = projectId, + updatedAt = updatedAt, + general = general.toDomain(), + face = hasFace().let { if (it) face.toDomain() else null }, + fingerprint = hasFingerprint().let { if (it) fingerprint.toDomain() else null }, + consent = consent.toDomain(), + identification = identification.toDomain(), + synchronization = synchronization.toDomain(), + multifactorId = multiFactorId?.toDomain(), + custom = customJson?.takeIf { it.isNotBlank() }?.let { try { JsonHelper.fromJson(it) } catch (_: Exception) { diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/SearchAndVerifyConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/MultiFactorIdConfiguration.kt similarity index 82% rename from infra/config-store/src/main/java/com/simprints/infra/config/store/models/SearchAndVerifyConfiguration.kt rename to infra/config-store/src/main/java/com/simprints/infra/config/store/models/MultiFactorIdConfiguration.kt index ee29b0528f..f315afa5f9 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/SearchAndVerifyConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/MultiFactorIdConfiguration.kt @@ -2,6 +2,6 @@ package com.simprints.infra.config.store.models import com.simprints.core.domain.externalcredential.ExternalCredentialType -data class SearchAndVerifyConfiguration( +data class MultiFactorIdConfiguration( val allowedExternalCredentials: List ) diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ProjectConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ProjectConfiguration.kt index 39ea770b3f..88af76ad8b 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ProjectConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/models/ProjectConfiguration.kt @@ -10,7 +10,7 @@ data class ProjectConfiguration( val consent: ConsentConfiguration, val identification: IdentificationConfiguration, val synchronization: SynchronizationConfiguration, - val searchAndVerify: SearchAndVerifyConfiguration?, + val multifactorId: MultiFactorIdConfiguration?, val custom: Map?, ) diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiMultiFactorIdConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiMultiFactorIdConfiguration.kt new file mode 100644 index 0000000000..47a1501851 --- /dev/null +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiMultiFactorIdConfiguration.kt @@ -0,0 +1,25 @@ +package com.simprints.infra.config.store.remote.models + +import androidx.annotation.Keep +import com.simprints.core.domain.externalcredential.ExternalCredentialType +import com.simprints.infra.config.store.models.MultiFactorIdConfiguration + +@Keep +internal data class ApiMultiFactorIdConfiguration( + val allowedExternalCredentials: List +) { + fun toDomain(): MultiFactorIdConfiguration = MultiFactorIdConfiguration( + allowedExternalCredentials = allowedExternalCredentials.map { it.toDomain() } + ) +} + +@Keep +enum class ApiExternalCredentialType { + NHISCard, GhanaIdCard, QRCode; + + fun toDomain(): ExternalCredentialType = when (this) { + NHISCard -> ExternalCredentialType.NHISCard + GhanaIdCard -> ExternalCredentialType.GhanaIdCard + QRCode -> ExternalCredentialType.QRCode + } +} diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiProjectConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiProjectConfiguration.kt index c782b25f79..696d4444fb 100644 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiProjectConfiguration.kt +++ b/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiProjectConfiguration.kt @@ -14,19 +14,20 @@ internal data class ApiProjectConfiguration( val consent: ApiConsentConfiguration, val identification: ApiIdentificationConfiguration, val synchronization: ApiSynchronizationConfiguration, - val searchAndVerify: ApiSearchAndVerifyConfiguration?, + val multiFactorId: ApiMultiFactorIdConfiguration?, val custom: Map?, ) { fun toDomain(): ProjectConfiguration = ProjectConfiguration( - id, - projectId, - updatedAt, - general.toDomain(), - face?.toDomain(), - fingerprint?.toDomain(), - consent.toDomain(), - identification.toDomain(), - synchronization.toDomain(), - custom, + id = id, + projectId = projectId, + updatedAt = updatedAt, + general = general.toDomain(), + face = face?.toDomain(), + fingerprint = fingerprint?.toDomain(), + consent = consent.toDomain(), + identification = identification.toDomain(), + synchronization = synchronization.toDomain(), + multifactorId = multiFactorId?.toDomain(), + custom = custom, ) } diff --git a/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSearchAndVerifyConfiguration.kt b/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSearchAndVerifyConfiguration.kt deleted file mode 100644 index 8b3eb3ca29..0000000000 --- a/infra/config-store/src/main/java/com/simprints/infra/config/store/remote/models/ApiSearchAndVerifyConfiguration.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.simprints.infra.config.store.remote.models - -import androidx.annotation.Keep -import com.simprints.core.domain.tokenization.asTokenizableEncrypted -import com.simprints.infra.config.store.models.DownSynchronizationConfiguration -import com.simprints.infra.config.store.models.DownSynchronizationConfiguration.Companion.DEFAULT_DOWN_SYNC_MAX_AGE -import com.simprints.infra.config.store.models.Frequency -import com.simprints.infra.config.store.models.SampleSynchronizationConfiguration -import com.simprints.infra.config.store.models.SynchronizationConfiguration -import com.simprints.infra.config.store.models.UpSynchronizationConfiguration - -@Keep -internal data class ApiSearchAndVerifyConfiguration( - val allowedExternalCredentials: List -) - -enum class ApiExternalCredentialType { - NHISCard, GhanaIdCard, QRCode -} - -fun mapToString(a: ApiExternalCredentialType) { - return when(a) - { - ApiExternalCredentialType.NHISCard -> TODO() - ApiExternalCredentialType.GhanaIdCard -> TODO() - ApiExternalCredentialType.QRCode -> TODO() - }} diff --git a/infra/config-store/src/main/proto/project_config.proto b/infra/config-store/src/main/proto/project_config.proto index b03ea409ce..69615d2745 100644 --- a/infra/config-store/src/main/proto/project_config.proto +++ b/infra/config-store/src/main/proto/project_config.proto @@ -14,6 +14,18 @@ message ProtoProjectConfiguration { string updated_at = 8; optional string customJson = 9; string id = 10; + optional ProtoMultiFactorIdConfiguration multiFactorId = 11; +} + +enum ProtoExternalCredentialType { + EXTERNAL_CREDENTIAL_TYPE_UNSPECIFIED = 0; + NHIS_CARD = 1; + GHANA_ID_CARD = 2; + QR_CODE = 3; +} + +message ProtoMultiFactorIdConfiguration { + repeated ProtoExternalCredentialType allowed_external_credentials = 1; } message ProtoGeneralConfiguration { 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 5a967181e1..ff6e0b7881 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 @@ -4,6 +4,7 @@ import androidx.annotation.Keep import com.simprints.core.ExcludedFromGeneratedTestCoverageReports import com.simprints.infra.enrolment.records.realm.store.BuildConfig import com.simprints.infra.enrolment.records.realm.store.migration.RealmMigrations +import com.simprints.infra.enrolment.records.realm.store.models.DbExternalCredential 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.DbProject @@ -26,6 +27,7 @@ class RealmConfig @Inject constructor() { DbFaceSample::class, DbSubject::class, DbProject::class, + DbExternalCredential::class, ), ).name("$databaseName.realm") .schemaVersion(REALM_SCHEMA_VERSION) @@ -36,6 +38,6 @@ class RealmConfig @Inject constructor() { .build() companion object { - private const val REALM_SCHEMA_VERSION: Long = 17 + private const val REALM_SCHEMA_VERSION: Long = 18 } } diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSource.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSource.kt index 1fe1969bc6..1b751691a4 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSource.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSource.kt @@ -226,12 +226,8 @@ internal class RealmEnrolmentRecordLocalDataSource @Inject constructor( val dbSubject: DbSubject? = realm.findSubject(RealmUUID.from(action.subjectId)) if (dbSubject != null) { val referencesToDelete = action.referenceIdsToRemove.toSet() // to make lookup O(1) - val externalCredentialsToAdd = action.externalCredentialsToAdd.toSet() // to make lookup O(1) val faceSamplesMap = dbSubject.faceSamples.groupBy { it.referenceId in referencesToDelete } val fingerprintSamplesMap = dbSubject.fingerprintSamples.groupBy { it.referenceId in referencesToDelete } -// val externalCredentialsMap = -// dbSubject.externalCredentials.groupBy { it.value in externalCredentialsToAdd.map { it.value.value } } - val allExternalCredentials = (dbSubject.externalCredentials + action.externalCredentialsToAdd.map { it.toRealmDb() }).distinctBy { it.id }.toSet() // Append new samples to the list of samples that remain after removing diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSource.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSource.kt index 505d9d323f..bf76f891a5 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSource.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSource.kt @@ -318,6 +318,9 @@ internal class RoomEnrolmentRecordLocalDataSource @Inject constructor( if (templatesToAdd.isNotEmpty()) { subjectDao.insertBiometricSamples(templatesToAdd) } + if (action.externalCredentialsToAdd.isNotEmpty()) { + subjectDao.insertExternalCredentials(action.externalCredentialsToAdd.map { it.toRoomDb() }) + } } else { Simber.e( "[updateSubject] Subject ${action.subjectId} not found for update", diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RoomExternalCredentialConverter.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RoomExternalCredentialConverter.kt index d43b2fe5af..9a1822ffaa 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RoomExternalCredentialConverter.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RoomExternalCredentialConverter.kt @@ -2,20 +2,17 @@ package com.simprints.infra.enrolment.records.repository.local.models import com.simprints.core.domain.externalcredential.ExternalCredential import com.simprints.core.domain.externalcredential.ExternalCredentialType -import com.simprints.core.domain.face.FaceSample -import com.simprints.core.domain.fingerprint.FingerprintSample -import com.simprints.infra.enrolment.records.room.store.models.DbBiometricTemplate +import com.simprints.core.domain.tokenization.asTokenizableEncrypted import com.simprints.infra.enrolment.records.room.store.models.DbExternalCredential -import com.simprints.infra.enrolment.records.room.store.models.Modality internal fun DbExternalCredential.toDomain(): ExternalCredential = ExternalCredential( - value = value, + value = value.asTokenizableEncrypted(), subjectId = subjectId, type = ExternalCredentialType.valueOf(type) ) internal fun ExternalCredential.toRoomDb(): DbExternalCredential = DbExternalCredential( - value = value, + value = value.value, subjectId = subjectId, type = type.name ) diff --git a/infra/enrolment-records/room-store/schemas/com.simprints.infra.enrolment.records.room.store.SubjectsDatabase/1.json b/infra/enrolment-records/room-store/schemas/com.simprints.infra.enrolment.records.room.store.SubjectsDatabase/1.json index bbe29315e1..58b0f3827c 100644 --- a/infra/enrolment-records/room-store/schemas/com.simprints.infra.enrolment.records.room.store.SubjectsDatabase/1.json +++ b/infra/enrolment-records/room-store/schemas/com.simprints.infra.enrolment.records.room.store.SubjectsDatabase/1.json @@ -177,4 +177,4 @@ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '94bee827928a2618c6873579bc6bc63a')" ] } -} \ No newline at end of file +} diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV2.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV2.kt index 92fd4a0598..f18557c181 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV2.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV2.kt @@ -25,6 +25,7 @@ internal data class ApiEnrolmentPayloadV2( override fun getTokenizedFieldJsonPath(tokenKeyType: TokenKeyType): String? = when (tokenKeyType) { TokenKeyType.AttendantId -> "attendantId" TokenKeyType.ModuleId -> "moduleId" + TokenKeyType.ExternalCredential -> "externalCredential" TokenKeyType.Unknown -> null } } 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 index 519e6ed6df..1cff72ca80 100644 --- 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 @@ -25,6 +25,7 @@ internal data class ApiEnrolmentPayloadV4( override fun getTokenizedFieldJsonPath(tokenKeyType: TokenKeyType): String? = when (tokenKeyType) { TokenKeyType.AttendantId -> "attendantId" TokenKeyType.ModuleId -> "moduleId" + TokenKeyType.ExternalCredential -> "externalCredential" TokenKeyType.Unknown -> null } } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV2.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV2.kt index 0475985a95..58bb4a4553 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV2.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV2.kt @@ -73,6 +73,7 @@ internal data class ApiCalloutPayloadV2( override fun getTokenizedFieldJsonPath(tokenKeyType: TokenKeyType): String? = when (tokenKeyType) { TokenKeyType.AttendantId -> "callout.userId" TokenKeyType.ModuleId -> "callout.moduleId" + TokenKeyType.ExternalCredential -> "callout.externalCredential" TokenKeyType.Unknown -> null } } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV3.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV3.kt index ab9f1a7cae..d703c11275 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV3.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV3.kt @@ -76,6 +76,7 @@ internal data class ApiCalloutPayloadV3( override fun getTokenizedFieldJsonPath(tokenKeyType: TokenKeyType): String? = when (tokenKeyType) { TokenKeyType.AttendantId -> "callout.userId" TokenKeyType.ModuleId -> "callout.moduleId" + TokenKeyType.ExternalCredential -> "callout.externalCredential" TokenKeyType.Unknown -> null } } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationPayload.kt index 3332ea9773..774d68c9d3 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationPayload.kt @@ -21,9 +21,10 @@ internal data class ApiEnrolmentRecordCreationPayload( ) : ApiEnrolmentRecordEventPayload(ApiEnrolmentRecordPayloadType.EnrolmentRecordCreation) internal fun ApiEnrolmentRecordCreationPayload.fromApiToDomain() = EnrolmentRecordCreationEvent.EnrolmentRecordCreationPayload( - subjectId, - projectId, - moduleId.asTokenizableEncrypted(), - attendantId.asTokenizableEncrypted(), - biometricReferences?.map { it.fromApiToDomain() } ?: emptyList(), + subjectId = subjectId, + projectId = projectId, + moduleId = moduleId.asTokenizableEncrypted(), + attendantId = attendantId.asTokenizableEncrypted(), + biometricReferences = biometricReferences?.map { it.fromApiToDomain() } ?: emptyList(), + externalCredential = externalCredential?.fromApiToDomain(subjectId) ) 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 1539651746..f3e09e0c18 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 @@ -266,6 +266,7 @@ internal class EventDownSyncTask @Inject constructor( faceSamplesToAdd = subjectFactory.extractFaceSamplesFromBiometricReferences(biometricReferencesAdded), fingerprintSamplesToAdd = subjectFactory.extractFingerprintSamplesFromBiometricReferences(biometricReferencesAdded), referenceIdsToRemove = biometricReferencesRemoved, + externalCredentialsToAdd = externalCredentialAdded?.let { listOf(it) } ?: emptyList() ), ) } diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/cosync/CoSyncEnrolmentRecordCreationEventDeserializer.kt b/infra/events/src/main/java/com/simprints/infra/events/event/cosync/CoSyncEnrolmentRecordCreationEventDeserializer.kt index e3fc850615..6f26ffe9ca 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/cosync/CoSyncEnrolmentRecordCreationEventDeserializer.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/cosync/CoSyncEnrolmentRecordCreationEventDeserializer.kt @@ -50,11 +50,13 @@ class CoSyncEnrolmentRecordCreationEventDeserializer : return EnrolmentRecordCreationEvent( id, EnrolmentRecordCreationEvent.EnrolmentRecordCreationPayload( - subjectId, - projectId, - moduleId, - attendantId, - biometricReferences, + subjectId = subjectId, + projectId = projectId, + moduleId = moduleId, + attendantId = attendantId, + biometricReferences = biometricReferences, + // TODO [CORE-3421] Update when CoSync supports external credentials (MfID) + externalCredential = null ), ) } 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 d9375fd3a1..9a89986e15 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 @@ -19,14 +19,16 @@ data class EnrolmentRecordCreationEvent( moduleId: TokenizableString, attendantId: TokenizableString, biometricReferences: List, + externalCredential: ExternalCredential?, ) : this( UUID.randomUUID().toString(), EnrolmentRecordCreationPayload( - subjectId, - projectId, - moduleId, - attendantId, - biometricReferences, + subjectId = subjectId, + projectId = projectId, + moduleId = moduleId, + attendantId = attendantId, + biometricReferences = biometricReferences, + externalCredential = externalCredential, ), ) From d61fb62f0c8dd0e98c1bee23e7203bc951fe3bee Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 10 Aug 2025 18:18:00 +0300 Subject: [PATCH 04/20] =?UTF-8?q?[CORE-3421]=20Fixing=20tests=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...RealmEnrolmentRecordLocalDataSourceTest.kt | 1 + .../RoomEnrolmentRecordLocalDataSourceTest.kt | 8 +++ .../1.json | 50 +++++++++++++++++-- .../models/ApiEnrolmentPayloadV2Test.kt | 3 +- .../ApiEnrolmentRecordCreationEventTest.kt | 32 ++++++++---- .../ApiEnrolmentRecordUpdateEventTest.kt | 25 +++++++--- .../sync/down/tasks/EventDownSyncTaskTest.kt | 29 ++++++++--- .../sync/down/tasks/SubjectFactoryTest.kt | 12 +++++ 8 files changed, 132 insertions(+), 28 deletions(-) diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSourceTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSourceTest.kt index 8508074a68..f16a5fbe70 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSourceTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSourceTest.kt @@ -316,6 +316,7 @@ class RealmEnrolmentRecordLocalDataSourceTest { faceSamplesToAdd = listOf(getRandomFaceSample()), fingerprintSamplesToAdd = listOf(getRandomFingerprintSample()), referenceIdsToRemove = listOf(faceReferenceId, fingerReferenceId), + externalCredentialsToAdd = listOf(), ), ), project, diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSourceTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSourceTest.kt index c916779534..d1c1644f7e 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSourceTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSourceTest.kt @@ -334,6 +334,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { faceSamplesToAdd = listOf(faceSample2), fingerprintSamplesToAdd = listOf(fingerprintSample1), referenceIdsToRemove = listOf(), + externalCredentialsToAdd = listOf(), ) // When @@ -365,6 +366,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { faceSamplesToAdd = listOf(), // Explicitly empty as in original fingerprintSamplesToAdd = listOf(), // Explicitly empty as in original referenceIdsToRemove = listOf(faceSample2.referenceId), + externalCredentialsToAdd = listOf(), ) // When @@ -394,6 +396,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { faceSamplesToAdd = listOf(), fingerprintSamplesToAdd = listOf(), referenceIdsToRemove = listOf(fingerprintSample2.referenceId), + externalCredentialsToAdd = listOf(), ) // When @@ -422,6 +425,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { faceSamplesToAdd = listOf(), fingerprintSamplesToAdd = listOf(), referenceIdsToRemove = listOf(faceSample1.referenceId), + externalCredentialsToAdd = listOf(), ) // When @@ -445,6 +449,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { faceSamplesToAdd = listOf(), fingerprintSamplesToAdd = listOf(), referenceIdsToRemove = listOf(fingerprintSample1.referenceId), + externalCredentialsToAdd = listOf(), ) // When @@ -464,6 +469,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { faceSamplesToAdd = listOf(faceSample1, faceSample2), fingerprintSamplesToAdd = listOf(fingerprintSample1), referenceIdsToRemove = listOf(), + externalCredentialsToAdd = listOf(), ) // When @@ -493,6 +499,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { faceSamplesToAdd = listOf(faceSample1), // Try to add samples fingerprintSamplesToAdd = listOf(), referenceIdsToRemove = listOf(), + externalCredentialsToAdd = listOf(), ) // When @@ -601,6 +608,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { faceSamplesToAdd = listOf(), fingerprintSamplesToAdd = listOf(fingerprintSample1), referenceIdsToRemove = listOf(), + externalCredentialsToAdd = listOf(), ) dataSource.performActions(listOf(updateAction), project) loadedSubject = diff --git a/infra/enrolment-records/room-store/schemas/com.simprints.infra.enrolment.records.room.store.SubjectsDatabase/1.json b/infra/enrolment-records/room-store/schemas/com.simprints.infra.enrolment.records.room.store.SubjectsDatabase/1.json index 58b0f3827c..365053e35b 100644 --- a/infra/enrolment-records/room-store/schemas/com.simprints.infra.enrolment.records.room.store.SubjectsDatabase/1.json +++ b/infra/enrolment-records/room-store/schemas/com.simprints.infra.enrolment.records.room.store.SubjectsDatabase/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "94bee827928a2618c6873579bc6bc63a", + "identityHash": "527fcc2c704906558681cb31beddb0c3", "entities": [ { "tableName": "DbSubject", @@ -170,11 +170,55 @@ ] } ] + }, + { + "tableName": "DbDbExternalCredential", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`value` TEXT NOT NULL, `subjectId` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`value`, `subjectId`), FOREIGN KEY(`subjectId`) REFERENCES `DbSubject`(`subjectId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "value", + "subjectId" + ] + }, + "foreignKeys": [ + { + "table": "DbSubject", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "subjectId" + ], + "referencedColumns": [ + "subjectId" + ] + } + ] } ], "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, '94bee827928a2618c6873579bc6bc63a')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '527fcc2c704906558681cb31beddb0c3')" ] } -} +} \ No newline at end of file diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV2Test.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV2Test.kt index 523977bddb..de5b9695a7 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV2Test.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/ApiEnrolmentPayloadV2Test.kt @@ -14,7 +14,8 @@ class ApiEnrolmentPayloadV2Test { when (it) { TokenKeyType.AttendantId -> assertThat(result).isEqualTo("attendantId") TokenKeyType.ModuleId -> assertThat(result).isEqualTo("moduleId") - else -> assertThat(result).isNull() + TokenKeyType.ExternalCredential -> assertThat(result).isEqualTo("externalCredential") + TokenKeyType.Unknown -> assertThat(result).isNull() } } } diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationEventTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationEventTest.kt index 72bda216d3..74fa6fa139 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationEventTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationEventTest.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models.subject import com.google.common.truth.Truth.assertThat +import com.simprints.core.domain.externalcredential.ExternalCredential +import com.simprints.core.domain.externalcredential.ExternalCredentialType import com.simprints.core.domain.fingerprint.IFingerIdentifier import com.simprints.core.domain.tokenization.asTokenizableEncrypted import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordCreationEvent @@ -14,11 +16,11 @@ class ApiEnrolmentRecordCreationEventTest { @Test fun convert_EnrolmentRecordCreationEvent() { val apiPayload = ApiEnrolmentRecordCreationPayload( - "subjectId", - "projectId", - "moduleId", - "attendantId", - listOf( + subjectId = "subjectId", + projectId = "projectId", + moduleId = "moduleId", + attendantId = "attendantId", + biometricReferences = listOf( ApiFingerprintReference( "fpRefId", listOf( @@ -27,13 +29,18 @@ class ApiEnrolmentRecordCreationEventTest { "NEC_1", ), ), + externalCredential = ApiExternalCredential( + id = "id", + type = ExternalCredentialType.NHISCard.toString(), + value = "value" + ), ) val expectedPayload = EnrolmentRecordCreationEvent.EnrolmentRecordCreationPayload( - "subjectId", - "projectId", - "moduleId".asTokenizableEncrypted(), - "attendantId".asTokenizableEncrypted(), - listOf( + subjectId = "subjectId", + projectId = "projectId", + moduleId = "moduleId".asTokenizableEncrypted(), + attendantId = "attendantId".asTokenizableEncrypted(), + biometricReferences = listOf( FingerprintReference( "fpRefId", listOf( @@ -42,6 +49,11 @@ class ApiEnrolmentRecordCreationEventTest { "NEC_1", ), ), + externalCredential = ExternalCredential( + value = "value".asTokenizableEncrypted(), + subjectId = "subjectId", + type = ExternalCredentialType.NHISCard + ), ) assertThat(apiPayload.fromApiToDomain()).isEqualTo(expectedPayload) 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 index 030c7bf493..e3ed24eef0 100644 --- 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 @@ -1,7 +1,10 @@ package com.simprints.infra.eventsync.event.remote.models.subject import com.google.common.truth.Truth.assertThat +import com.simprints.core.domain.externalcredential.ExternalCredential +import com.simprints.core.domain.externalcredential.ExternalCredentialType import com.simprints.core.domain.fingerprint.IFingerIdentifier +import com.simprints.core.domain.tokenization.asTokenizableEncrypted 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 @@ -17,8 +20,8 @@ class ApiEnrolmentRecordUpdateEventTest { @Test fun convert_EnrolmentRecordUpdateEvent() { val apiPayload = ApiEnrolmentRecordUpdatePayload( - "subjectId", - listOf( + subjectId = "subjectId", + biometricReferencesAdded = listOf( ApiFingerprintReference( "fpRefId", listOf( @@ -32,11 +35,16 @@ class ApiEnrolmentRecordUpdateEventTest { "ROC_3", ), ), - listOf("fpRefId2"), + biometricReferencesRemoved = listOf("fpRefId2"), + externalCredentialAdded = ApiExternalCredential( + id = "id", + type = ExternalCredentialType.NHISCard.toString(), + value = "value" + ) ) val expectedPayload = EnrolmentRecordUpdateEvent.EnrolmentRecordUpdatePayload( - "subjectId", - listOf( + subjectId = "subjectId", + biometricReferencesAdded = listOf( FingerprintReference( "fpRefId", listOf(FingerprintTemplate("template", IFingerIdentifier.LEFT_THUMB)), @@ -48,7 +56,12 @@ class ApiEnrolmentRecordUpdateEventTest { "ROC_3", ), ), - listOf("fpRefId2"), + biometricReferencesRemoved = listOf("fpRefId2"), + externalCredentialAdded = ExternalCredential( + value = "value".asTokenizableEncrypted(), + subjectId = "subjectId", + type = ExternalCredentialType.NHISCard + ) ) assertThat(apiPayload.fromApiToDomain()).isEqualTo(expectedPayload) 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 a7d6b01de8..3962485c2b 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,7 +1,10 @@ package com.simprints.infra.eventsync.sync.down.tasks import com.google.common.truth.Truth.assertThat +import com.simprints.core.domain.externalcredential.ExternalCredential +import com.simprints.core.domain.externalcredential.ExternalCredentialType import com.simprints.core.domain.face.FaceSample +import com.simprints.core.domain.tokenization.asTokenizableEncrypted import com.simprints.core.domain.tokenization.asTokenizableRaw import com.simprints.core.tools.time.TimeHelper import com.simprints.infra.authstore.exceptions.RemoteDbNotSignedInException @@ -58,11 +61,16 @@ class EventDownSyncTaskTest { "attendantId", ) val ENROLMENT_RECORD_CREATION = EnrolmentRecordCreationEvent( - "subjectId", - "projectId", - "moduleId".asTokenizableRaw(), - "attendantId".asTokenizableRaw(), - listOf(FaceReference("id", listOf(FaceTemplate("template")), "format")), + subjectId = "subjectId", + projectId = "projectId", + moduleId = "moduleId".asTokenizableRaw(), + attendantId = "attendantId".asTokenizableRaw(), + biometricReferences = listOf(FaceReference("id", listOf(FaceTemplate("template")), "format")), + externalCredential = ExternalCredential( + value = "value".asTokenizableEncrypted(), + subjectId = "subjectId", + type = ExternalCredentialType.NHISCard + ), ) val ENROLMENT_RECORD_MOVE_MODULE = EnrolmentRecordMoveEvent( EnrolmentRecordMoveEvent.EnrolmentRecordCreationInMove( @@ -110,9 +118,14 @@ class EventDownSyncTaskTest { ), ) val ENROLMENT_RECORD_UPDATE = EnrolmentRecordUpdateEvent( - "subjectId", - listOf(FaceReference("id", listOf(FaceTemplate("template")), "format")), - listOf("referenceIdToDelete"), + subjectId = "subjectId", + biometricReferencesAdded = listOf(FaceReference("id", listOf(FaceTemplate("template")), "format")), + biometricReferencesRemoved = listOf("referenceIdToDelete"), + externalCredentialAdded = ExternalCredential( + value = "value".asTokenizableEncrypted(), + subjectId = "subjectId", + type = ExternalCredentialType.NHISCard + ) ) } 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 684a61f533..29fe72c30c 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 @@ -1,9 +1,12 @@ package com.simprints.infra.eventsync.sync.down.tasks import com.google.common.truth.Truth.assertThat +import com.simprints.core.domain.externalcredential.ExternalCredential +import com.simprints.core.domain.externalcredential.ExternalCredentialType 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.core.tools.time.TimeHelper import com.simprints.core.tools.utils.EncodingUtils @@ -61,6 +64,7 @@ class SubjectFactoryTest { attendantId = ATTENDANT_ID, moduleId = MODULE_ID, biometricReferences = listOf(FINGERPRINT_REFERENCE, faceReference), + externalCredential = null ) val result = factory.buildSubjectFromCreationPayload(payload) val expected = Subject( @@ -177,6 +181,7 @@ class SubjectFactoryTest { templates = listOf(FaceTemplate(template = BASE_64_BYTES.toString())), ), ), + externalCredentialAdded = EXTERNAL_CREDENTIAL ) val result = factory.buildSubjectFromUpdatePayload(subject, payload) @@ -326,6 +331,8 @@ class SubjectFactoryTest { private const val REFERENCE_ID = "fpRefId" private const val REFERENCE_FORMAT = "NEC_1" private const val TEMPLATE_NAME = "template" + private val EXTERNAL_CREDENTIAL_VALUE = "value".asTokenizableEncrypted() + private val EXTERNAL_CREDENTIAL_TYPE = ExternalCredentialType.NHISCard private val IDENTIFIER = IFingerIdentifier.LEFT_THUMB private const val QUALITY = 10 private val FINGERPRINT_REFERENCE = FingerprintReference( @@ -343,5 +350,10 @@ class SubjectFactoryTest { templates = listOf(FaceTemplate(TEMPLATE_NAME)), format = REFERENCE_FORMAT, ) + private val EXTERNAL_CREDENTIAL = ExternalCredential( + value = EXTERNAL_CREDENTIAL_VALUE, + subjectId = SUBJECT_ID, + type = EXTERNAL_CREDENTIAL_TYPE + ) } } From 0660608816f0fc722910f8c688ce8442685d75fe Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 10 Aug 2025 18:37:29 +0300 Subject: [PATCH 05/20] =?UTF-8?q?[CORE-3421]=20Fixing=20tests=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../authenticator/ProjectAuthenticatorTest.kt | 19 +++--- .../authenticator/SignerManagerTest.kt | 21 +++--- .../local/ConfigLocalDataSourceImplTest.kt | 22 ++++--- .../models/ApiProjectConfigurationTest.kt | 44 +++++++------ .../infra/config/store/testtools/Models.kt | 65 +++++++++++++------ 5 files changed, 102 insertions(+), 69 deletions(-) diff --git a/infra/auth-logic/src/test/java/com/simprints/infra/authlogic/authenticator/ProjectAuthenticatorTest.kt b/infra/auth-logic/src/test/java/com/simprints/infra/authlogic/authenticator/ProjectAuthenticatorTest.kt index 968f04c4dc..e38b099a26 100644 --- a/infra/auth-logic/src/test/java/com/simprints/infra/authlogic/authenticator/ProjectAuthenticatorTest.kt +++ b/infra/auth-logic/src/test/java/com/simprints/infra/authlogic/authenticator/ProjectAuthenticatorTest.kt @@ -164,9 +164,9 @@ class ProjectAuthenticatorTest { } returns Token("", "", "", "") coEvery { configManager.getProjectConfiguration() } returns ProjectConfiguration( - "id", - PROJECT_ID, - "", + id = "id", + projectId = PROJECT_ID, + updatedAt = "", general = GeneralConfiguration( modalities = mockk(), matchingModalities = mockk(), @@ -176,12 +176,13 @@ class ProjectAuthenticatorTest { duplicateBiometricEnrolmentCheck = false, settingsPassword = mockk(), ), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), + face = mockk(), + fingerprint = mockk(), + consent = mockk(), + identification = mockk(), + synchronization = mockk(), + multifactorId = mockk(), + custom = mockk(), ) coEvery { configManager.getPrivacyNotice(any(), any()) } returns emptyFlow() diff --git a/infra/auth-logic/src/test/java/com/simprints/infra/authlogic/authenticator/SignerManagerTest.kt b/infra/auth-logic/src/test/java/com/simprints/infra/authlogic/authenticator/SignerManagerTest.kt index 5ba9343990..af8a2c6dc8 100644 --- a/infra/auth-logic/src/test/java/com/simprints/infra/authlogic/authenticator/SignerManagerTest.kt +++ b/infra/auth-logic/src/test/java/com/simprints/infra/authlogic/authenticator/SignerManagerTest.kt @@ -227,16 +227,17 @@ internal class SignerManagerTest { tokenizationKeys = emptyMap(), ), ProjectConfiguration( - "id", - DEFAULT_PROJECT_ID, - "", - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - null, + id = "id", + projectId = DEFAULT_PROJECT_ID, + updatedAt = "", + general = mockk(), + face = mockk(), + fingerprint = mockk(), + consent = mockk(), + identification = mockk(), + synchronization = mockk(), + multifactorId = mockk(), + custom = mockk(), ), ), ) diff --git a/infra/config-store/src/test/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImplTest.kt b/infra/config-store/src/test/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImplTest.kt index 68378448af..6b70f71501 100644 --- a/infra/config-store/src/test/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImplTest.kt +++ b/infra/config-store/src/test/java/com/simprints/infra/config/store/local/ConfigLocalDataSourceImplTest.kt @@ -17,6 +17,7 @@ import com.simprints.infra.config.store.testtools.consentConfiguration import com.simprints.infra.config.store.testtools.faceConfiguration import com.simprints.infra.config.store.testtools.generalConfiguration import com.simprints.infra.config.store.testtools.identificationConfiguration +import com.simprints.infra.config.store.testtools.multiFactorIdConfiguration import com.simprints.infra.config.store.testtools.project import com.simprints.infra.config.store.testtools.projectConfiguration import com.simprints.infra.config.store.testtools.synchronizationConfiguration @@ -173,16 +174,17 @@ class ConfigLocalDataSourceImplTest { fun `should save the project configuration and update the device configuration correctly with an empty list of fingersToCollect if fingerprint config is missing`() = runTest { val projectConfigurationToSave = ProjectConfiguration( - "id", - "projectId", - "updatedAt", - generalConfiguration, - faceConfiguration, - null, - consentConfiguration, - identificationConfiguration, - synchronizationConfiguration, - null, + id = "id", + projectId = "projectId", + updatedAt = "updatedAt", + general = generalConfiguration, + face = faceConfiguration, + fingerprint = null, + consent = consentConfiguration, + identification = identificationConfiguration, + synchronization = synchronizationConfiguration, + multifactorId = multiFactorIdConfiguration, + custom = null, ) configLocalDataSourceImpl.saveProjectConfiguration(projectConfigurationToSave) diff --git a/infra/config-store/src/test/java/com/simprints/infra/config/store/remote/models/ApiProjectConfigurationTest.kt b/infra/config-store/src/test/java/com/simprints/infra/config/store/remote/models/ApiProjectConfigurationTest.kt index f5b2a3e4e2..1e48265958 100644 --- a/infra/config-store/src/test/java/com/simprints/infra/config/store/remote/models/ApiProjectConfigurationTest.kt +++ b/infra/config-store/src/test/java/com/simprints/infra/config/store/remote/models/ApiProjectConfigurationTest.kt @@ -5,12 +5,14 @@ import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.config.store.testtools.apiConsentConfiguration import com.simprints.infra.config.store.testtools.apiGeneralConfiguration import com.simprints.infra.config.store.testtools.apiIdentificationConfiguration +import com.simprints.infra.config.store.testtools.apiMultiFactorIdConfiguration import com.simprints.infra.config.store.testtools.apiProjectConfiguration import com.simprints.infra.config.store.testtools.apiSynchronizationConfiguration import com.simprints.infra.config.store.testtools.consentConfiguration import com.simprints.infra.config.store.testtools.customKeyMap import com.simprints.infra.config.store.testtools.generalConfiguration import com.simprints.infra.config.store.testtools.identificationConfiguration +import com.simprints.infra.config.store.testtools.multiFactorIdConfiguration import com.simprints.infra.config.store.testtools.projectConfiguration import com.simprints.infra.config.store.testtools.synchronizationConfiguration import org.junit.Test @@ -24,28 +26,30 @@ class ApiProjectConfigurationTest { @Test fun `should map correctly the model when both fingerprint and face are missing`() { val apiProjectConfiguration = ApiProjectConfiguration( - "id", - "projectId", - "updatedAt", - apiGeneralConfiguration, - null, - null, - apiConsentConfiguration, - apiIdentificationConfiguration, - apiSynchronizationConfiguration, - customKeyMap, + id = "id", + projectId = "projectId", + updatedAt = "updatedAt", + general = apiGeneralConfiguration, + face = null, + fingerprint = null, + consent = apiConsentConfiguration, + identification = apiIdentificationConfiguration, + synchronization = apiSynchronizationConfiguration, + multiFactorId = apiMultiFactorIdConfiguration, + custom = customKeyMap, ) val projectConfiguration = ProjectConfiguration( - "id", - "projectId", - "updatedAt", - generalConfiguration, - null, - null, - consentConfiguration, - identificationConfiguration, - synchronizationConfiguration, - customKeyMap, + id = "id", + projectId = "projectId", + updatedAt = "updatedAt", + general = generalConfiguration, + face = null, + fingerprint = null, + consent = consentConfiguration, + identification = identificationConfiguration, + synchronization = synchronizationConfiguration, + multifactorId = multiFactorIdConfiguration, + custom = customKeyMap, ) assertThat(apiProjectConfiguration.toDomain()).isEqualTo(projectConfiguration) diff --git a/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt b/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt index 229040bc55..b141abc1b5 100644 --- a/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt +++ b/infra/config-store/src/test/java/com/simprints/infra/config/store/testtools/Models.kt @@ -1,17 +1,20 @@ package com.simprints.infra.config.store.testtools +import com.simprints.core.domain.externalcredential.ExternalCredentialType import com.simprints.core.domain.tokenization.asTokenizableEncrypted import com.simprints.infra.config.store.local.models.ProtoAllowedAgeRange import com.simprints.infra.config.store.local.models.ProtoConsentConfiguration import com.simprints.infra.config.store.local.models.ProtoDecisionPolicy import com.simprints.infra.config.store.local.models.ProtoDeviceConfiguration import com.simprints.infra.config.store.local.models.ProtoDownSynchronizationConfiguration +import com.simprints.infra.config.store.local.models.ProtoExternalCredentialType import com.simprints.infra.config.store.local.models.ProtoFaceConfiguration import com.simprints.infra.config.store.local.models.ProtoFinger import com.simprints.infra.config.store.local.models.ProtoFingerprintConfiguration import com.simprints.infra.config.store.local.models.ProtoFingerprintConfiguration.ProtoMaxCaptureAttempts import com.simprints.infra.config.store.local.models.ProtoGeneralConfiguration import com.simprints.infra.config.store.local.models.ProtoIdentificationConfiguration +import com.simprints.infra.config.store.local.models.ProtoMultiFactorIdConfiguration import com.simprints.infra.config.store.local.models.ProtoProject import com.simprints.infra.config.store.local.models.ProtoProjectConfiguration import com.simprints.infra.config.store.local.models.ProtoSampleSynchronizationConfiguration @@ -35,6 +38,7 @@ import com.simprints.infra.config.store.models.Frequency import com.simprints.infra.config.store.models.GeneralConfiguration import com.simprints.infra.config.store.models.IdentificationConfiguration import com.simprints.infra.config.store.models.MaxCaptureAttempts +import com.simprints.infra.config.store.models.MultiFactorIdConfiguration import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.config.store.models.ProjectState @@ -49,12 +53,14 @@ import com.simprints.infra.config.store.remote.models.ApiAllowedAgeRange import com.simprints.infra.config.store.remote.models.ApiConsentConfiguration import com.simprints.infra.config.store.remote.models.ApiDecisionPolicy import com.simprints.infra.config.store.remote.models.ApiDeviceState +import com.simprints.infra.config.store.remote.models.ApiExternalCredentialType import com.simprints.infra.config.store.remote.models.ApiFaceConfiguration import com.simprints.infra.config.store.remote.models.ApiFaceConfiguration.ApiFaceSdkConfiguration import com.simprints.infra.config.store.remote.models.ApiFingerprintConfiguration import com.simprints.infra.config.store.remote.models.ApiGeneralConfiguration import com.simprints.infra.config.store.remote.models.ApiIdentificationConfiguration import com.simprints.infra.config.store.remote.models.ApiMaxCaptureAttempts +import com.simprints.infra.config.store.remote.models.ApiMultiFactorIdConfiguration import com.simprints.infra.config.store.remote.models.ApiProject import com.simprints.infra.config.store.remote.models.ApiProjectConfiguration import com.simprints.infra.config.store.remote.models.ApiProjectState @@ -405,6 +411,16 @@ internal val synchronizationConfiguration = SynchronizationConfiguration( ), ) +internal val allowedExternalCredential = ExternalCredentialType.NHISCard + +internal val multiFactorIdConfiguration = MultiFactorIdConfiguration( + allowedExternalCredentials = listOf(allowedExternalCredential) +) + +internal val protoMultiFactorIdConfiguration = ProtoMultiFactorIdConfiguration + .newBuilder() + .addAllowedExternalCredentials(ProtoExternalCredentialType.NHIS_CARD) + internal val protoSynchronizationConfiguration = ProtoSynchronizationConfiguration .newBuilder() .setUp( @@ -451,6 +467,12 @@ internal val protoSynchronizationConfiguration = ProtoSynchronizationConfigurati .build(), ).build() +internal val apiAllowedExternalCredential = ApiExternalCredentialType.NHISCard + +internal val apiMultiFactorIdConfiguration = ApiMultiFactorIdConfiguration( + allowedExternalCredentials = listOf(apiAllowedExternalCredential) +) + internal val customKeyMap: Map? = mapOf( "key1" to 7, "key2" to 4.2, @@ -460,29 +482,31 @@ internal val customKeyMap: Map? = mapOf( internal const val PROTO_CUSTOM_KEY_MAP_JSON = "{\"key1\":7,\"key2\":4.2,\"key3\":false,\"key4\":\"test\"}" internal val apiProjectConfiguration = ApiProjectConfiguration( - "id", - "projectId", - "updatedAt", - apiGeneralConfiguration, - apiFaceConfiguration, - apiFingerprintConfiguration, - apiConsentConfiguration, - apiIdentificationConfiguration, - apiSynchronizationConfiguration, - customKeyMap, + id = "id", + projectId = "projectId", + updatedAt = "updatedAt", + general = apiGeneralConfiguration, + face = apiFaceConfiguration, + fingerprint = apiFingerprintConfiguration, + consent = apiConsentConfiguration, + identification = apiIdentificationConfiguration, + synchronization = apiSynchronizationConfiguration, + multiFactorId = apiMultiFactorIdConfiguration, + custom = customKeyMap, ) internal val projectConfiguration = ProjectConfiguration( - "id", - "projectId", - "updatedAt", - generalConfiguration, - faceConfiguration, - fingerprintConfiguration, - consentConfiguration, - identificationConfiguration, - synchronizationConfiguration, - customKeyMap, + id = "id", + projectId = "projectId", + updatedAt = "updatedAt", + general = generalConfiguration, + face = faceConfiguration, + fingerprint = fingerprintConfiguration, + consent = consentConfiguration, + identification = identificationConfiguration, + synchronization = synchronizationConfiguration, + multifactorId = multiFactorIdConfiguration, + custom = customKeyMap ) internal val protoProjectConfiguration = ProtoProjectConfiguration @@ -496,6 +520,7 @@ internal val protoProjectConfiguration = ProtoProjectConfiguration .setConsent(protoConsentConfiguration) .setIdentification(protoIdentificationConfiguration) .setSynchronization(protoSynchronizationConfiguration) + .setMultiFactorId(protoMultiFactorIdConfiguration) .setCustomJson(PROTO_CUSTOM_KEY_MAP_JSON) .build() From 5317e06d5131defc1b2e5f6dda447104cc47ded5 Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 10 Aug 2025 18:57:03 +0300 Subject: [PATCH 06/20] =?UTF-8?q?[CORE-3421]=20Fixing=20tests=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/sync/config/testtools/Models.kt | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt b/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt index aa055eb0b9..2810ef06ed 100644 --- a/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt +++ b/infra/sync/src/test/java/com/simprints/infra/sync/config/testtools/Models.kt @@ -1,5 +1,6 @@ package com.simprints.infra.sync.config.testtools +import com.simprints.core.domain.externalcredential.ExternalCredentialType import com.simprints.core.domain.tokenization.asTokenizableEncrypted import com.simprints.infra.config.store.models.AgeGroup import com.simprints.infra.config.store.models.ConsentConfiguration @@ -12,6 +13,7 @@ import com.simprints.infra.config.store.models.Frequency import com.simprints.infra.config.store.models.GeneralConfiguration import com.simprints.infra.config.store.models.IdentificationConfiguration import com.simprints.infra.config.store.models.MaxCaptureAttempts +import com.simprints.infra.config.store.models.MultiFactorIdConfiguration import com.simprints.infra.config.store.models.Project import com.simprints.infra.config.store.models.ProjectConfiguration import com.simprints.infra.config.store.models.ProjectState @@ -114,6 +116,12 @@ internal val simprintsDownSyncConfigurationConfiguration = DownSynchronizationCo frequency = Frequency.PERIODICALLY, ) +internal val allowedExternalCredential = ExternalCredentialType.NHISCard + +internal val multiFactorIdConfiguration = MultiFactorIdConfiguration( + allowedExternalCredentials = listOf(allowedExternalCredential) +) + internal val synchronizationConfiguration = SynchronizationConfiguration( up = UpSynchronizationConfiguration( simprints = simprintsUpSyncConfigurationConfiguration, @@ -131,16 +139,17 @@ internal val identificationConfiguration = IdentificationConfiguration(4, IdentificationConfiguration.PoolType.PROJECT) internal val projectConfiguration = ProjectConfiguration( - "id", - "projectId", - "updatedAt", - generalConfiguration, - faceConfiguration, - fingerprintConfiguration, - consentConfiguration, - identificationConfiguration, - synchronizationConfiguration, - null, + id = "id", + projectId = "projectId", + updatedAt = "updatedAt", + general = generalConfiguration, + face = faceConfiguration, + fingerprint = fingerprintConfiguration, + consent = consentConfiguration, + identification = identificationConfiguration, + synchronization = synchronizationConfiguration, + multifactorId = multiFactorIdConfiguration, + custom = null ) internal const val TOKENIZATION_JSON = From 54f7ddf37f5309b3de742c93db672ec3c50b893f Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 10 Aug 2025 19:30:37 +0300 Subject: [PATCH 07/20] =?UTF-8?q?[CORE-3421]=20Fixing=20tests=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enrollast/screen/usecase/BuildSubjectUseCase.kt | 7 +++++++ .../usecases/response/CreateEnrolResponseUseCase.kt | 4 ++++ .../local/RealmEnrolmentRecordLocalDataSourceTest.kt | 12 ++++++++++++ .../event/remote/models/ApiEnrolmentPayloadV4.kt | 2 ++ .../remote/models/callout/ApiCalloutPayloadV2.kt | 2 ++ .../remote/models/callout/ApiCalloutPayloadV3.kt | 2 ++ .../eventsync/sync/down/tasks/SubjectFactory.kt | 7 +++++++ .../models/subject/EnrolmentRecordCreationEvent.kt | 2 ++ .../models/subject/EnrolmentRecordUpdateEvent.kt | 2 ++ 9 files changed, 40 insertions(+) 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 ba78166670..b537dda31c 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 @@ -1,5 +1,6 @@ package com.simprints.feature.enrollast.screen.usecase +import com.simprints.core.domain.externalcredential.ExternalCredential import com.simprints.core.domain.face.FaceSample import com.simprints.core.domain.fingerprint.FingerprintSample import com.simprints.core.domain.fingerprint.IFingerIdentifier @@ -31,8 +32,14 @@ internal class BuildSubjectUseCase @Inject constructor( faceSamples = getFaceCaptureResult(params.steps) ?.let { result -> result.results.map { faceSample(result.referenceId, it) } } .orEmpty(), + externalCredential = getExternalCredentialResult(params.steps) ) + // TODO [CORE-3421] When an external credential can be extracted from the UI-level steps, extract it here + private fun getExternalCredentialResult(steps: List): ExternalCredential? { + return null + } + private fun getFingerprintCaptureResult(steps: List) = steps .filterIsInstance() .firstOrNull() diff --git a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateEnrolResponseUseCase.kt b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateEnrolResponseUseCase.kt index 69d2b57198..53f4dc9a2f 100644 --- a/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateEnrolResponseUseCase.kt +++ b/feature/orchestrator/src/main/java/com/simprints/feature/orchestrator/usecases/response/CreateEnrolResponseUseCase.kt @@ -1,5 +1,6 @@ package com.simprints.feature.orchestrator.usecases.response +import com.simprints.core.domain.externalcredential.ExternalCredential import com.simprints.core.domain.response.AppErrorReason import com.simprints.face.capture.FaceCaptureResult import com.simprints.fingerprint.capture.FingerprintCaptureResult @@ -25,6 +26,8 @@ internal class CreateEnrolResponseUseCase @Inject constructor( ): AppResponse { val fingerprintCapture = results.filterIsInstance(FingerprintCaptureResult::class.java).lastOrNull() val faceCapture = results.filterIsInstance(FaceCaptureResult::class.java).lastOrNull() + // TODO [CORE-3421] When an external credential can be extracted from the UI-level steps, extract it here + val externalCredential: ExternalCredential? = null return try { val subject = subjectFactory.buildSubjectFromCaptureResults( @@ -33,6 +36,7 @@ internal class CreateEnrolResponseUseCase @Inject constructor( moduleId = request.moduleId, fingerprintResponse = fingerprintCapture, faceResponse = faceCapture, + externalCredential = externalCredential, ) enrolSubject(subject, project) diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSourceTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSourceTest.kt index f16a5fbe70..dd6bdc4ff4 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSourceTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RealmEnrolmentRecordLocalDataSourceTest.kt @@ -1,6 +1,8 @@ package com.simprints.infra.enrolment.records.repository.local import com.google.common.truth.Truth.* +import com.simprints.core.domain.externalcredential.ExternalCredential +import com.simprints.core.domain.externalcredential.ExternalCredentialType import com.simprints.core.domain.face.FaceSample import com.simprints.core.domain.fingerprint.FingerprintSample import com.simprints.core.domain.fingerprint.IFingerIdentifier @@ -431,6 +433,9 @@ class RealmEnrolmentRecordLocalDataSourceTest { getRandomFaceSample(), ), fingerprintSamples: List = listOf(), + externalCredentials: List = listOf( + getRandomExternalCredential() + ), ): Subject = Subject( subjectId = patientId, projectId = projectId, @@ -438,6 +443,7 @@ class RealmEnrolmentRecordLocalDataSourceTest { moduleId = moduleId.asTokenizableRaw(), faceSamples = faceSamples, fingerprintSamples = fingerprintSamples, + externalCredentials = externalCredentials ) private fun getRandomFaceSample( @@ -449,4 +455,10 @@ class RealmEnrolmentRecordLocalDataSourceTest { id: String = UUID.randomUUID().toString(), referenceId: String = "referenceId", ) = FingerprintSample(IFingerIdentifier.LEFT_3RD_FINGER, Random.nextBytes(64), "fingerprintTemplateFormat", referenceId, id) + + private fun getRandomExternalCredential() = ExternalCredential( + value = "value".asTokenizableEncrypted(), + subjectId = "subjectId", + type = ExternalCredentialType.NHISCard + ) } 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 index 1cff72ca80..28368915c6 100644 --- 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 @@ -1,10 +1,12 @@ package com.simprints.infra.eventsync.event.remote.models import androidx.annotation.Keep +import com.simprints.core.ExcludedFromGeneratedTestCoverageReports import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.EnrolmentEventV4 @Keep +@ExcludedFromGeneratedTestCoverageReports("Data class") internal data class ApiEnrolmentPayloadV4( override val startTime: ApiTimestamp, val subjectId: String, diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV2.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV2.kt index 58bb4a4553..45441ef474 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV2.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV2.kt @@ -3,6 +3,7 @@ package com.simprints.infra.eventsync.event.remote.models.callout import androidx.annotation.Keep import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonInclude.Include +import com.simprints.core.ExcludedFromGeneratedTestCoverageReports import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.callout.ConfirmationCalloutEventV2.ConfirmationCalloutPayload import com.simprints.infra.events.event.domain.models.callout.EnrolmentCalloutEventV2.EnrolmentCalloutPayload @@ -15,6 +16,7 @@ import com.simprints.infra.eventsync.event.remote.models.fromDomainToApi @Keep @JsonInclude(Include.NON_NULL) +@ExcludedFromGeneratedTestCoverageReports("Data class") internal data class ApiCalloutPayloadV2( override val startTime: ApiTimestamp, val callout: ApiCallout, diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV3.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV3.kt index d703c11275..373e28cd0f 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV3.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/callout/ApiCalloutPayloadV3.kt @@ -3,6 +3,7 @@ package com.simprints.infra.eventsync.event.remote.models.callout import androidx.annotation.Keep import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonInclude.Include +import com.simprints.core.ExcludedFromGeneratedTestCoverageReports import com.simprints.infra.config.store.models.TokenKeyType import com.simprints.infra.events.event.domain.models.callout.ConfirmationCalloutEventV3.ConfirmationCalloutPayload import com.simprints.infra.events.event.domain.models.callout.EnrolmentCalloutEventV3.EnrolmentCalloutPayload @@ -15,6 +16,7 @@ import com.simprints.infra.eventsync.event.remote.models.fromDomainToApi @Keep @JsonInclude(Include.NON_NULL) +@ExcludedFromGeneratedTestCoverageReports("Data class") internal data class ApiCalloutPayloadV3( override val startTime: ApiTimestamp, val callout: ApiCallout, 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 8c5ce2496e..6b360f69ec 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 @@ -1,5 +1,6 @@ package com.simprints.infra.eventsync.sync.down.tasks +import com.simprints.core.domain.externalcredential.ExternalCredential import com.simprints.core.domain.face.FaceSample import com.simprints.core.domain.fingerprint.FingerprintSample import com.simprints.core.domain.tokenization.TokenizableString @@ -32,6 +33,7 @@ class SubjectFactory @Inject constructor( moduleId = moduleId, fingerprintSamples = extractFingerprintSamplesFromBiometricReferences(this.biometricReferences), faceSamples = extractFaceSamplesFromBiometricReferences(this.biometricReferences), + externalCredential = payload.externalCredential, ) } @@ -43,6 +45,7 @@ class SubjectFactory @Inject constructor( moduleId = moduleId, fingerprintSamples = extractFingerprintSamplesFromBiometricReferences(this.biometricReferences), faceSamples = extractFaceSamplesFromBiometricReferences(this.biometricReferences), + externalCredential = null, ) } @@ -70,6 +73,7 @@ class SubjectFactory @Inject constructor( moduleId: TokenizableString, fingerprintResponse: FingerprintCaptureResult?, faceResponse: FaceCaptureResult?, + externalCredential: ExternalCredential?, ): Subject { val subjectId = UUID.randomUUID().toString() return buildSubject( @@ -80,6 +84,7 @@ class SubjectFactory @Inject constructor( createdAt = Date(timeHelper.now().ms), fingerprintSamples = fingerprintResponse?.let { extractFingerprintSamples(it) }.orEmpty(), faceSamples = faceResponse?.let { extractFaceSamples(it) }.orEmpty(), + externalCredential = externalCredential, ) } @@ -92,6 +97,7 @@ class SubjectFactory @Inject constructor( updatedAt: Date? = null, fingerprintSamples: List = emptyList(), faceSamples: List = emptyList(), + externalCredential: ExternalCredential? = null, ) = Subject( subjectId = subjectId, projectId = projectId, @@ -101,6 +107,7 @@ class SubjectFactory @Inject constructor( updatedAt = updatedAt, fingerprintSamples = fingerprintSamples, faceSamples = faceSamples, + externalCredentials = externalCredential?.let { listOf(it) } ?: emptyList() ) private fun extractFingerprintSamples(fingerprintResponse: FingerprintCaptureResult) = 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 9a89986e15..3fc585095a 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,6 +1,7 @@ package com.simprints.infra.events.event.domain.models.subject import androidx.annotation.Keep +import com.simprints.core.ExcludedFromGeneratedTestCoverageReports import com.simprints.core.domain.externalcredential.ExternalCredential import com.simprints.core.domain.face.FaceSample import com.simprints.core.domain.fingerprint.FingerprintSample @@ -9,6 +10,7 @@ import com.simprints.core.tools.utils.EncodingUtils import java.util.UUID @Keep +@ExcludedFromGeneratedTestCoverageReports("Data class") data class EnrolmentRecordCreationEvent( override val id: String, val payload: EnrolmentRecordCreationPayload, 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 index cd50c3ffd0..34c9196462 100644 --- 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 @@ -1,10 +1,12 @@ package com.simprints.infra.events.event.domain.models.subject import androidx.annotation.Keep +import com.simprints.core.ExcludedFromGeneratedTestCoverageReports import com.simprints.core.domain.externalcredential.ExternalCredential import java.util.UUID @Keep +@ExcludedFromGeneratedTestCoverageReports("Data class") data class EnrolmentRecordUpdateEvent( override val id: String, val payload: EnrolmentRecordUpdatePayload, From d80695984c5822505f70cc07a0b0b70c707dfdf3 Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 10 Aug 2025 19:48:21 +0300 Subject: [PATCH 08/20] =?UTF-8?q?[CORE-3421]=20Fixing=20tests=CB=86=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RoomEnrolmentRecordLocalDataSourceTest.kt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSourceTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSourceTest.kt index d1c1644f7e..d1302a8cd3 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSourceTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSourceTest.kt @@ -3,6 +3,8 @@ package com.simprints.infra.enrolment.records.repository.local import android.content.Context import androidx.test.core.app.ApplicationProvider import com.google.common.truth.Truth.assertThat +import com.simprints.core.domain.externalcredential.ExternalCredential +import com.simprints.core.domain.externalcredential.ExternalCredentialType import com.simprints.core.domain.face.FaceSample import com.simprints.core.domain.fingerprint.FingerprintSample import com.simprints.core.domain.fingerprint.IFingerIdentifier @@ -65,6 +67,14 @@ class RoomEnrolmentRecordLocalDataSourceTest { // --- Test Data --- private val date = Date() // Use a fixed date for consistent timestamps in tests + // External credentials + private val externalCredential = ExternalCredential( + value = "value".asTokenizableEncrypted(), + subjectId = "subjectId", + type = ExternalCredentialType.NHISCard + ) + + // Samples defined first private val faceSample1 = FaceSample( template = byteArrayOf(1, 2, 3), @@ -116,6 +126,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { fingerprintSamples = emptyList(), createdAt = date, updatedAt = date, + externalCredentials = listOf(getExternalCredential("subj-001")) ) private val subject2P1WithFinger = Subject( subjectId = "subj-002", @@ -126,6 +137,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { fingerprintSamples = listOf(fingerprintSample1), createdAt = date, updatedAt = date, + externalCredentials = listOf(getExternalCredential("subj-002")) ) private val subject3P1WithBoth = Subject( subjectId = "subj-003", @@ -134,6 +146,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { moduleId = MODULE_2_ID, faceSamples = listOf(faceSample2), fingerprintSamples = listOf(fingerprintSample2), + externalCredentials = listOf(getExternalCredential("subj-003")) ) private val subject4P2WithBoth = Subject( subjectId = "subj-004", @@ -144,6 +157,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { fingerprintSamples = listOf(fingerprintSample3), createdAt = date, updatedAt = date, + externalCredentials = listOf(getExternalCredential("subj-004")) ) private val subject5P2WithFace = Subject( // Added subject @@ -155,6 +169,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { fingerprintSamples = emptyList(), createdAt = Date(date.time + 1000), // Slightly different time updatedAt = Date(date.time + 1000), + externalCredentials = listOf(getExternalCredential("subj-005")) ) private val subject6P2WithFinger = Subject( // Added subject @@ -166,6 +181,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { fingerprintSamples = listOf(fingerprintSample3.copy(id = UUID.randomUUID().toString())), createdAt = Date(date.time + 2000), // Different time updatedAt = Date(date.time + 2000), + externalCredentials = listOf(getExternalCredential("subj-006")) ) private val subjectInvalidNoSamples = Subject( subjectId = "subj-invalid", @@ -174,6 +190,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { moduleId = MODULE_1_ID, createdAt = date, updatedAt = date, + externalCredentials = listOf(getExternalCredential("subj-invalid")) ) private val project: Project = mockk() @@ -213,6 +230,12 @@ class RoomEnrolmentRecordLocalDataSourceTest { SubjectAction.Creation(subject6P2WithFinger), ) + private fun getExternalCredential(subjectId: String) = ExternalCredential( + value = "value".asTokenizableEncrypted(), + subjectId = subjectId, + type = ExternalCredentialType.NHISCard + ) + private suspend fun setupInitialData() { dataSource.performActions( initialData, From 6aedb12aebd404507d7fd7d9e384bb80063b8772 Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 10 Aug 2025 20:26:51 +0300 Subject: [PATCH 09/20] [CORE-3421] Fixing tests --- .../response/CreateEnrolResponseUseCaseTest.kt | 18 ++++++++++++++++-- .../sync/down/tasks/SubjectFactoryTest.kt | 4 ++++ 2 files changed, 20 insertions(+), 2 deletions(-) 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 476ef8afec..4112ef3859 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 @@ -49,7 +49,14 @@ internal class CreateEnrolResponseUseCaseTest { @Test fun `Converts correct results to response`() = runTest { every { - subjectFactory.buildSubjectFromCaptureResults(any(), any(), any(), any(), any()) + subjectFactory.buildSubjectFromCaptureResults( + projectId = any(), + attendantId = any(), + moduleId = any(), + fingerprintResponse = any(), + faceResponse = any(), + externalCredential = any() + ) } returns mockk { every { subjectId } returns "guid" } assertThat( @@ -68,7 +75,14 @@ internal class CreateEnrolResponseUseCaseTest { @Test fun `Returns error if no valid response`() = runTest { every { - subjectFactory.buildSubjectFromCaptureResults(any(), any(), any(), null, null) + subjectFactory.buildSubjectFromCaptureResults( + projectId = any(), + attendantId = any(), + moduleId = any(), + fingerprintResponse = null, + faceResponse = null, + externalCredential = null + ) } throws MissingCaptureException() assertThat(useCase(action, emptyList(), project)).isInstanceOf(AppErrorResponse::class.java) 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 29fe72c30c..0b470f6a68 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 @@ -245,6 +245,7 @@ class SubjectFactoryTest { referenceId = REFERENCE_ID, ), ), + externalCredentials = listOf(EXTERNAL_CREDENTIAL) ) val result = factory.buildSubjectFromCaptureResults( @@ -282,6 +283,7 @@ class SubjectFactoryTest { ), ), ), + externalCredential = EXTERNAL_CREDENTIAL ) assertThat(result).isEqualTo(expected) } @@ -308,6 +310,7 @@ class SubjectFactoryTest { referenceId = REFERENCE_ID, ), ), + externalCredentials = listOf(EXTERNAL_CREDENTIAL) ) val result = factory.buildSubject( @@ -317,6 +320,7 @@ class SubjectFactoryTest { moduleId = expected.moduleId, fingerprintSamples = expected.fingerprintSamples, faceSamples = expected.faceSamples, + externalCredential = expected.externalCredentials.first() ) assertThat(result).isEqualTo(expected) } From 23a6c01807efc22309ac52977b8e6315da96b8eb Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 12 Aug 2025 12:30:54 +0300 Subject: [PATCH 10/20] =?UTF-8?q?[CORE-3421]=20Adding=201->2=20Room=20migr?= =?UTF-8?q?ation=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/build.gradle.kts | 1 + .../room/store/migration/Migration1to2Test.kt | 42 +++++++++++++++++++ .../records/room/store/SubjectsDatabase.kt | 7 +++- .../room/store/migration/RoomMigrations.kt | 27 ++++++++++++ 4 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/room/store/migration/Migration1to2Test.kt create mode 100644 infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/migration/RoomMigrations.kt diff --git a/infra/enrolment-records/repository/build.gradle.kts b/infra/enrolment-records/repository/build.gradle.kts index e87c549e8a..085e5cbe1f 100644 --- a/infra/enrolment-records/repository/build.gradle.kts +++ b/infra/enrolment-records/repository/build.gradle.kts @@ -17,4 +17,5 @@ dependencies { implementation(libs.libsimprints) implementation(libs.retrofit.core) implementation(libs.jackson.core) + implementation(libs.testing.androidX.room) } diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/room/store/migration/Migration1to2Test.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/room/store/migration/Migration1to2Test.kt new file mode 100644 index 0000000000..7d10aa6065 --- /dev/null +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/room/store/migration/Migration1to2Test.kt @@ -0,0 +1,42 @@ +package com.simprints.infra.enrolment.records.room.store.migration + +import androidx.room.testing.MigrationTestHelper +import androidx.test.ext.junit.runners.* +import androidx.test.platform.app.* +import com.google.common.truth.Truth.* +import com.simprints.infra.enrolment.records.room.store.SubjectsDatabase +import org.junit.Rule +import org.junit.runner.RunWith +import kotlin.test.Test + +@RunWith(AndroidJUnit4::class) +class Migration1to2Test { + + @get:Rule + val helper = MigrationTestHelper( + InstrumentationRegistry.getInstrumentation(), + SubjectsDatabase::class.java, + ) + + @Test + fun `when migrating from 1 to 2 then external credential table should be added`() { + val db1 = helper.createDatabase(name = TEST_DB, version = 1) + val db2 = helper.runMigrationsAndValidate( + name = TEST_DB, + version = 2, + validateDroppedTables = true, + MIGRATION_1_2 + ) + + // Verify external credentials table exists + val cursor = db2.query("SELECT name FROM sqlite_master WHERE name='DbDbExternalCredential'") + assertThat(cursor.count).isEqualTo(1) + cursor.close() + db1.close() + db2.close() + } + + companion object { + private const val TEST_DB = "migration-test" + } +} diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectsDatabase.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectsDatabase.kt index 7b4e9f3641..ca12b4fca9 100644 --- a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectsDatabase.kt +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectsDatabase.kt @@ -5,6 +5,7 @@ import androidx.annotation.Keep import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase +import com.simprints.infra.enrolment.records.room.store.migration.MIGRATION_1_2 import com.simprints.infra.enrolment.records.room.store.models.DbBiometricTemplate import com.simprints.infra.enrolment.records.room.store.models.DbExternalCredential import com.simprints.infra.enrolment.records.room.store.models.DbSubject @@ -18,7 +19,7 @@ import javax.inject.Singleton DbBiometricTemplate::class, DbExternalCredential::class, ], - version = 1, + version = 2, exportSchema = true, ) @Keep @@ -31,7 +32,9 @@ abstract class SubjectsDatabase : RoomDatabase() { factory: SupportOpenHelperFactory, dbName: String, ): SubjectsDatabase { - val builder = Room.databaseBuilder(context, SubjectsDatabase::class.java, dbName) + val builder = Room + .databaseBuilder(context, SubjectsDatabase::class.java, dbName) + .addMigrations(MIGRATION_1_2) if (BuildConfig.DB_ENCRYPTION) { builder.openHelperFactory(factory) } diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/migration/RoomMigrations.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/migration/RoomMigrations.kt new file mode 100644 index 0000000000..e59ce0f544 --- /dev/null +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/migration/RoomMigrations.kt @@ -0,0 +1,27 @@ +package com.simprints.infra.enrolment.records.room.store.migration + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +/** + * Schema version 1 -> 2 + * + * Changes: + * - Adding [DbExternalCredential] entity + * */ +val MIGRATION_1_2 = object : Migration(1, 2) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS `DbDbExternalCredential` ( + `value` TEXT NOT NULL, + `subjectId` TEXT NOT NULL, + `type` TEXT NOT NULL, + PRIMARY KEY(`value`, `subjectId`), + FOREIGN KEY(`subjectId`) REFERENCES `DbSubject`(`subjectId`) ON DELETE CASCADE + ) + """.trimIndent() + ) + } +} + From 7c2c5ff6627fc9a6cdf383a279f38004439f53b5 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 12 Aug 2025 12:44:55 +0300 Subject: [PATCH 11/20] [CORE-3421] Moving Room DB version to a static field so that it can be dynamically referenced in the tests --- .../local/RoomEnrolmentRecordLocalDataSourceTest.kt | 3 ++- .../infra/enrolment/records/room/store/SubjectsDatabase.kt | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSourceTest.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSourceTest.kt index d1302a8cd3..5a37523cff 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSourceTest.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/repository/local/RoomEnrolmentRecordLocalDataSourceTest.kt @@ -15,6 +15,7 @@ import com.simprints.infra.enrolment.records.repository.domain.models.BiometricD import com.simprints.infra.enrolment.records.repository.domain.models.Subject import com.simprints.infra.enrolment.records.repository.domain.models.SubjectAction import com.simprints.infra.enrolment.records.repository.domain.models.SubjectQuery +import com.simprints.infra.enrolment.records.room.store.SubjectsDatabase.Companion.SUBJECT_DB_VERSION import com.simprints.infra.enrolment.records.room.store.SubjectsDatabaseFactory import com.simprints.infra.security.keyprovider.LocalDbKey import com.simprints.testtools.common.coroutines.TestCoroutineRule @@ -1484,7 +1485,7 @@ class RoomEnrolmentRecordLocalDataSourceTest { val result = dataSource.getLocalDBInfo() // Then assertThat(result).contains("Database Name: db-subjects") - assertThat(result).contains("Database Version: 1") + assertThat(result).contains("Database Version: $SUBJECT_DB_VERSION") assertThat(result).contains("Is Encrypted: false") // db not encrypted in tests assertThat(result).contains("Number of Subjects: 6") } diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectsDatabase.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectsDatabase.kt index ca12b4fca9..422c0dc33c 100644 --- a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectsDatabase.kt +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectsDatabase.kt @@ -5,6 +5,7 @@ import androidx.annotation.Keep import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase +import com.simprints.infra.enrolment.records.room.store.SubjectsDatabase.Companion.SUBJECT_DB_VERSION import com.simprints.infra.enrolment.records.room.store.migration.MIGRATION_1_2 import com.simprints.infra.enrolment.records.room.store.models.DbBiometricTemplate import com.simprints.infra.enrolment.records.room.store.models.DbExternalCredential @@ -19,7 +20,7 @@ import javax.inject.Singleton DbBiometricTemplate::class, DbExternalCredential::class, ], - version = 2, + version = SUBJECT_DB_VERSION, exportSchema = true, ) @Keep @@ -40,5 +41,6 @@ abstract class SubjectsDatabase : RoomDatabase() { } return builder.build() } + const val SUBJECT_DB_VERSION = 2 } } From 3036a3aac00ccc0cdf615094c375208a80c476b4 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 12 Aug 2025 13:22:17 +0300 Subject: [PATCH 12/20] [CORE-3421] Updating tests --- .../infra/eventsync/sync/down/tasks/EventDownSyncTaskTest.kt | 3 +++ 1 file changed, 3 insertions(+) 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 3962485c2b..0933803c1f 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 @@ -532,6 +532,9 @@ class EventDownSyncTaskTest { faceSamples = listOf( FaceSample(byteArrayOf(), "format", "referenceId"), ), + externalCredentials = listOf( + ExternalCredential(value = "value".asTokenizableEncrypted(), subjectId = "subjectId", type = ExternalCredentialType.NHISCard) + ) ), ) From f0d359ca3867b7d83d8136be3c185b9731d7cd51 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 12 Aug 2025 13:22:31 +0300 Subject: [PATCH 13/20] [CORE-3421] Removing unused classes --- .../repository/ExternalCredentialRepository.kt | 8 -------- .../enrolment/records/room/store/SubjectDao.kt | 1 - .../store/models/SubjectExternalCredentials.kt | 14 -------------- 3 files changed, 23 deletions(-) delete mode 100644 infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/ExternalCredentialRepository.kt delete mode 100644 infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectExternalCredentials.kt diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/ExternalCredentialRepository.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/ExternalCredentialRepository.kt deleted file mode 100644 index aac8a37e6a..0000000000 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/ExternalCredentialRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.simprints.infra.enrolment.records.repository - -import javax.inject.Inject - -internal class ExternalCredentialRepository @Inject constructor( - -){ -} diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt index d682a67b2a..e331ae3ebc 100644 --- a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt @@ -11,7 +11,6 @@ import com.simprints.infra.enrolment.records.room.store.models.DbBiometricTempla import com.simprints.infra.enrolment.records.room.store.models.DbExternalCredential import com.simprints.infra.enrolment.records.room.store.models.DbSubject import com.simprints.infra.enrolment.records.room.store.models.SubjectBiometrics -import com.simprints.infra.enrolment.records.room.store.models.SubjectExternalCredentials @Dao interface SubjectDao { diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectExternalCredentials.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectExternalCredentials.kt deleted file mode 100644 index f710dfa45a..0000000000 --- a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectExternalCredentials.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.simprints.infra.enrolment.records.room.store.models - -import androidx.room.Embedded -import androidx.room.Relation -import com.simprints.infra.enrolment.records.room.store.models.DbSubject.Companion.SUBJECT_ID_COLUMN - -data class SubjectExternalCredentials( - @Embedded val subject: DbSubject, - @Relation( - parentColumn = SUBJECT_ID_COLUMN, - entityColumn = SUBJECT_ID_COLUMN, - ) - val externalCredentials: List, -) From e445a48028295c8b1835d2c602238decca83d0a0 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 25 Aug 2025 17:05:17 +0300 Subject: [PATCH 14/20] [CORE-3421] External credentials are now stored as list on the EnrolmentRecordCreationEvent. Renaming `DbDbExternalCredential` to `DbExternalCredential` --- ...GetEnrolmentCreationEventForSubjectUseCase.kt | 2 +- .../screen/usecase/BuildSubjectUseCase.kt | 2 +- .../room/store/migration/Migration1to2Test.kt | 2 +- .../1.json | 4 ++-- .../enrolment/records/room/store/SubjectDao.kt | 4 ++-- .../room/store/migration/RoomMigrations.kt | 2 +- .../room/store/models/DbExternalCredential.kt | 2 +- .../subject/ApiEnrolmentRecordCreationPayload.kt | 2 +- .../eventsync/sync/down/tasks/SubjectFactory.kt | 10 +++++----- .../sync/down/tasks/EventDownSyncTaskTest.kt | 16 +++++++++++----- ...ncEnrolmentRecordCreationEventDeserializer.kt | 2 +- .../subject/EnrolmentRecordCreationEvent.kt | 6 +++--- 12 files changed, 30 insertions(+), 24 deletions(-) diff --git a/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/GetEnrolmentCreationEventForSubjectUseCase.kt b/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/GetEnrolmentCreationEventForSubjectUseCase.kt index 40359a9ac5..cba8da95f5 100644 --- a/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/GetEnrolmentCreationEventForSubjectUseCase.kt +++ b/feature/client-api/src/main/java/com/simprints/feature/clientapi/usecases/GetEnrolmentCreationEventForSubjectUseCase.kt @@ -53,7 +53,7 @@ internal class GetEnrolmentCreationEventForSubjectUseCase @Inject constructor( attendantId = attendantId, biometricReferences = EnrolmentRecordCreationEvent.buildBiometricReferences(fingerprintSamples, faceSamples, encoder), // TODO [CORE-3421] Review if EnrolmentRecordCreationEvent should contain List of external credentials, as it currently doesn't make sense - externalCredential = externalCredentials.firstOrNull() + externalCredentials = externalCredentials ) companion object { 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 b537dda31c..b232c0c3e4 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 @@ -32,7 +32,7 @@ internal class BuildSubjectUseCase @Inject constructor( faceSamples = getFaceCaptureResult(params.steps) ?.let { result -> result.results.map { faceSample(result.referenceId, it) } } .orEmpty(), - externalCredential = getExternalCredentialResult(params.steps) + externalCredentials = getExternalCredentialResult(params.steps)?.let { listOf(it) } ?: emptyList() ) // TODO [CORE-3421] When an external credential can be extracted from the UI-level steps, extract it here diff --git a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/room/store/migration/Migration1to2Test.kt b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/room/store/migration/Migration1to2Test.kt index 7d10aa6065..5cda6f65fe 100644 --- a/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/room/store/migration/Migration1to2Test.kt +++ b/infra/enrolment-records/repository/src/test/java/com/simprints/infra/enrolment/records/room/store/migration/Migration1to2Test.kt @@ -29,7 +29,7 @@ class Migration1to2Test { ) // Verify external credentials table exists - val cursor = db2.query("SELECT name FROM sqlite_master WHERE name='DbDbExternalCredential'") + val cursor = db2.query("SELECT name FROM sqlite_master WHERE name='DbExternalCredential'") assertThat(cursor.count).isEqualTo(1) cursor.close() db1.close() diff --git a/infra/enrolment-records/room-store/schemas/com.simprints.infra.enrolment.records.room.store.SubjectsDatabase/1.json b/infra/enrolment-records/room-store/schemas/com.simprints.infra.enrolment.records.room.store.SubjectsDatabase/1.json index 365053e35b..4df59295d1 100644 --- a/infra/enrolment-records/room-store/schemas/com.simprints.infra.enrolment.records.room.store.SubjectsDatabase/1.json +++ b/infra/enrolment-records/room-store/schemas/com.simprints.infra.enrolment.records.room.store.SubjectsDatabase/1.json @@ -172,7 +172,7 @@ ] }, { - "tableName": "DbDbExternalCredential", + "tableName": "DbExternalCredential", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`value` TEXT NOT NULL, `subjectId` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`value`, `subjectId`), FOREIGN KEY(`subjectId`) REFERENCES `DbSubject`(`subjectId`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { @@ -221,4 +221,4 @@ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '527fcc2c704906558681cb31beddb0c3')" ] } -} \ No newline at end of file +} diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt index e331ae3ebc..0648f165f2 100644 --- a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt @@ -30,7 +30,7 @@ interface SubjectDao { @Query("DELETE FROM DbBiometricTemplate WHERE uuid = :uuid") suspend fun deleteBiometricSample(uuid: String) - @Query("DELETE FROM DbDbExternalCredential WHERE value = :value") + @Query("DELETE FROM DbExternalCredential WHERE value = :value") suspend fun deleteExternalCredential(value: String) @RawQuery @@ -42,7 +42,7 @@ interface SubjectDao { @RawQuery suspend fun loadSubjects(query: SupportSQLiteQuery): List - @Query("SELECT * FROM DbDbExternalCredential") + @Query("SELECT * FROM DbExternalCredential") suspend fun getAllExternalCredentials(): List @RawQuery diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/migration/RoomMigrations.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/migration/RoomMigrations.kt index e59ce0f544..106bbdc2a3 100644 --- a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/migration/RoomMigrations.kt +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/migration/RoomMigrations.kt @@ -13,7 +13,7 @@ val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL( """ - CREATE TABLE IF NOT EXISTS `DbDbExternalCredential` ( + CREATE TABLE IF NOT EXISTS `DbExternalCredential` ( `value` TEXT NOT NULL, `subjectId` TEXT NOT NULL, `type` TEXT NOT NULL, diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/DbExternalCredential.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/DbExternalCredential.kt index 7468c1dbc2..f476ef190c 100644 --- a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/DbExternalCredential.kt +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/DbExternalCredential.kt @@ -28,6 +28,6 @@ data class DbExternalCredential( ) { companion object { const val EXTERNAL_CREDENTIAL_VALUE_COLUMN = "value" - const val EXTERNAL_CREDENTIAL_TABLE_NAME = "DbDbExternalCredential" + const val EXTERNAL_CREDENTIAL_TABLE_NAME = "DbExternalCredential" } } diff --git a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationPayload.kt b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationPayload.kt index 774d68c9d3..dc5b0e7178 100644 --- a/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationPayload.kt +++ b/infra/event-sync/src/main/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationPayload.kt @@ -26,5 +26,5 @@ internal fun ApiEnrolmentRecordCreationPayload.fromApiToDomain() = EnrolmentReco moduleId = moduleId.asTokenizableEncrypted(), attendantId = attendantId.asTokenizableEncrypted(), biometricReferences = biometricReferences?.map { it.fromApiToDomain() } ?: emptyList(), - externalCredential = externalCredential?.fromApiToDomain(subjectId) + externalCredentials = externalCredential?.let { listOf(it.fromApiToDomain(subjectId)) } ?: emptyList() ) 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 6b360f69ec..38eaf5d2e6 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 @@ -33,7 +33,7 @@ class SubjectFactory @Inject constructor( moduleId = moduleId, fingerprintSamples = extractFingerprintSamplesFromBiometricReferences(this.biometricReferences), faceSamples = extractFaceSamplesFromBiometricReferences(this.biometricReferences), - externalCredential = payload.externalCredential, + externalCredentials = payload.externalCredentials, ) } @@ -45,7 +45,7 @@ class SubjectFactory @Inject constructor( moduleId = moduleId, fingerprintSamples = extractFingerprintSamplesFromBiometricReferences(this.biometricReferences), faceSamples = extractFaceSamplesFromBiometricReferences(this.biometricReferences), - externalCredential = null, + externalCredentials = emptyList(), ) } @@ -84,7 +84,7 @@ class SubjectFactory @Inject constructor( createdAt = Date(timeHelper.now().ms), fingerprintSamples = fingerprintResponse?.let { extractFingerprintSamples(it) }.orEmpty(), faceSamples = faceResponse?.let { extractFaceSamples(it) }.orEmpty(), - externalCredential = externalCredential, + externalCredentials = externalCredential?.let { listOf(it) } ?: emptyList(), ) } @@ -97,7 +97,7 @@ class SubjectFactory @Inject constructor( updatedAt: Date? = null, fingerprintSamples: List = emptyList(), faceSamples: List = emptyList(), - externalCredential: ExternalCredential? = null, + externalCredentials: List = emptyList(), ) = Subject( subjectId = subjectId, projectId = projectId, @@ -107,7 +107,7 @@ class SubjectFactory @Inject constructor( updatedAt = updatedAt, fingerprintSamples = fingerprintSamples, faceSamples = faceSamples, - externalCredentials = externalCredential?.let { listOf(it) } ?: emptyList() + externalCredentials = externalCredentials ) private fun extractFingerprintSamples(fingerprintResponse: FingerprintCaptureResult) = 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 0933803c1f..57b852bfca 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 @@ -66,10 +66,12 @@ class EventDownSyncTaskTest { moduleId = "moduleId".asTokenizableRaw(), attendantId = "attendantId".asTokenizableRaw(), biometricReferences = listOf(FaceReference("id", listOf(FaceTemplate("template")), "format")), - externalCredential = ExternalCredential( - value = "value".asTokenizableEncrypted(), - subjectId = "subjectId", - type = ExternalCredentialType.NHISCard + externalCredentials = listOf( + ExternalCredential( + value = "value".asTokenizableEncrypted(), + subjectId = "subjectId", + type = ExternalCredentialType.NHISCard + ) ), ) val ENROLMENT_RECORD_MOVE_MODULE = EnrolmentRecordMoveEvent( @@ -533,7 +535,11 @@ class EventDownSyncTaskTest { FaceSample(byteArrayOf(), "format", "referenceId"), ), externalCredentials = listOf( - ExternalCredential(value = "value".asTokenizableEncrypted(), subjectId = "subjectId", type = ExternalCredentialType.NHISCard) + ExternalCredential( + value = "value".asTokenizableEncrypted(), + subjectId = "subjectId", + type = ExternalCredentialType.NHISCard + ) ) ), ) diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/cosync/CoSyncEnrolmentRecordCreationEventDeserializer.kt b/infra/events/src/main/java/com/simprints/infra/events/event/cosync/CoSyncEnrolmentRecordCreationEventDeserializer.kt index 6f26ffe9ca..df5939165b 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/cosync/CoSyncEnrolmentRecordCreationEventDeserializer.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/cosync/CoSyncEnrolmentRecordCreationEventDeserializer.kt @@ -56,7 +56,7 @@ class CoSyncEnrolmentRecordCreationEventDeserializer : attendantId = attendantId, biometricReferences = biometricReferences, // TODO [CORE-3421] Update when CoSync supports external credentials (MfID) - externalCredential = null + externalCredentials = emptyList() ), ) } 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 3fc585095a..3e17b20424 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 @@ -21,7 +21,7 @@ data class EnrolmentRecordCreationEvent( moduleId: TokenizableString, attendantId: TokenizableString, biometricReferences: List, - externalCredential: ExternalCredential?, + externalCredentials: List, ) : this( UUID.randomUUID().toString(), EnrolmentRecordCreationPayload( @@ -30,7 +30,7 @@ data class EnrolmentRecordCreationEvent( moduleId = moduleId, attendantId = attendantId, biometricReferences = biometricReferences, - externalCredential = externalCredential, + externalCredentials = externalCredentials, ), ) @@ -41,7 +41,7 @@ data class EnrolmentRecordCreationEvent( val moduleId: TokenizableString, val attendantId: TokenizableString, val biometricReferences: List, - val externalCredential: ExternalCredential? + val externalCredentials: List ) companion object { From b372abe4556b75991acba58d904fe2e3ec784aa8 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 25 Aug 2025 17:13:57 +0300 Subject: [PATCH 15/20] [CORE-3421] Moving external credential Realm mapper to its separate file --- .../local/models/RealmExternalCredentialConverter.kt | 10 ++++++++++ .../local/models/RealmFingerprintSampleConverter.kt | 8 -------- 2 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RealmExternalCredentialConverter.kt diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RealmExternalCredentialConverter.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RealmExternalCredentialConverter.kt new file mode 100644 index 0000000000..b002d06ac6 --- /dev/null +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RealmExternalCredentialConverter.kt @@ -0,0 +1,10 @@ +package com.simprints.infra.enrolment.records.repository.local.models + +import com.simprints.core.domain.externalcredential.ExternalCredential +import com.simprints.infra.enrolment.records.realm.store.models.DbExternalCredential as RealmExternalCredential + +internal fun ExternalCredential.toRealmDb(): RealmExternalCredential = RealmExternalCredential().also { sample -> + sample.value = value.value + sample.subjectId = subjectId + sample.type = type.toString() +} diff --git a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RealmFingerprintSampleConverter.kt b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RealmFingerprintSampleConverter.kt index ebfab4e594..6b2be097ba 100644 --- a/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RealmFingerprintSampleConverter.kt +++ b/infra/enrolment-records/repository/src/main/java/com/simprints/infra/enrolment/records/repository/local/models/RealmFingerprintSampleConverter.kt @@ -1,9 +1,7 @@ package com.simprints.infra.enrolment.records.repository.local.models -import com.simprints.core.domain.externalcredential.ExternalCredential import com.simprints.core.domain.fingerprint.FingerprintSample import com.simprints.infra.enrolment.records.realm.store.models.DbFingerprintSample as RealmFingerprintSample -import com.simprints.infra.enrolment.records.realm.store.models.DbExternalCredential as RealmExternalCredential internal fun RealmFingerprintSample.toDomain(): FingerprintSample = FingerprintSample( id = id, @@ -20,9 +18,3 @@ internal fun FingerprintSample.toRealmDb(): RealmFingerprintSample = RealmFinger sample.template = template sample.format = format } - -internal fun ExternalCredential.toRealmDb(): RealmExternalCredential = RealmExternalCredential().also { sample -> - sample.value = value.value - sample.subjectId = subjectId - sample.type = type.toString() -} From b715b1bfa140055621f0a909dea6e1549f45d96b Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 25 Aug 2025 17:35:16 +0300 Subject: [PATCH 16/20] =?UTF-8?q?[CORE-3421]=20EnrolmentRecordMoveEvent=20?= =?UTF-8?q?now=20contains=20external=20credential=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../records/room/store/SubjectDao.kt | 1 - .../subject/ApiEnrolmentRecordMovePayload.kt | 20 ++++++++++--------- .../sync/down/tasks/SubjectFactory.kt | 2 +- .../subject/EnrolmentRecordMoveEvent.kt | 2 ++ 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt index 0648f165f2..ffdba547e4 100644 --- a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/SubjectDao.kt @@ -14,7 +14,6 @@ import com.simprints.infra.enrolment.records.room.store.models.SubjectBiometrics @Dao interface SubjectDao { - /*Remaining method*/ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertSubject(subject: DbSubject) 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 87a8b7d67a..53e5492ab7 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 @@ -31,25 +31,27 @@ internal data class ApiEnrolmentRecordMovePayload( val moduleId: String, val attendantId: String, val biometricReferences: List?, + val externalCredential: ApiExternalCredential?, ) } internal fun ApiEnrolmentRecordMovePayload.fromApiToDomain() = EnrolmentRecordMoveEvent.EnrolmentRecordMovePayload( with(enrolmentRecordCreation) { EnrolmentRecordCreationInMove( - subjectId, - projectId, - moduleId.asTokenizableEncrypted(), - attendantId.asTokenizableEncrypted(), - biometricReferences?.map { it.fromApiToDomain() }, + subjectId = subjectId, + projectId = projectId, + moduleId = moduleId.asTokenizableEncrypted(), + attendantId = attendantId.asTokenizableEncrypted(), + biometricReferences = biometricReferences?.map { it.fromApiToDomain() }, + externalCredential = externalCredential?.fromApiToDomain(subjectId) ) }, enrolmentRecordDeletion.let { EnrolmentRecordDeletionInMove( - it.subjectId, - it.projectId, - it.moduleId.asTokenizableEncrypted(), - it.attendantId.asTokenizableEncrypted(), + subjectId = it.subjectId, + projectId = it.projectId, + moduleId = it.moduleId.asTokenizableEncrypted(), + attendantId = it.attendantId.asTokenizableEncrypted(), ) }, ) 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 38eaf5d2e6..920e0cf22e 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 @@ -45,7 +45,7 @@ class SubjectFactory @Inject constructor( moduleId = moduleId, fingerprintSamples = extractFingerprintSamplesFromBiometricReferences(this.biometricReferences), faceSamples = extractFaceSamplesFromBiometricReferences(this.biometricReferences), - externalCredentials = emptyList(), + externalCredentials = externalCredential?.let { listOf(it) } ?: emptyList(), ) } diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordMoveEvent.kt b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordMoveEvent.kt index 32dcd17da6..21392b5452 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordMoveEvent.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/domain/models/subject/EnrolmentRecordMoveEvent.kt @@ -1,6 +1,7 @@ package com.simprints.infra.events.event.domain.models.subject import androidx.annotation.Keep +import com.simprints.core.domain.externalcredential.ExternalCredential import com.simprints.core.domain.tokenization.TokenizableString import java.util.UUID @@ -35,5 +36,6 @@ data class EnrolmentRecordMoveEvent( val moduleId: TokenizableString, val attendantId: TokenizableString, val biometricReferences: List?, + val externalCredential: ExternalCredential?, ) } From 16e166a641f1b7fb95a848e1f07b11b0a45f84b7 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 25 Aug 2025 17:39:19 +0300 Subject: [PATCH 17/20] [CORE-3421] Reverting addition of the external credentials in SubjectBiometrics class --- .../records/room/store/models/SubjectBiometrics.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectBiometrics.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectBiometrics.kt index d267894645..1ea2f722d2 100644 --- a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectBiometrics.kt +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectBiometrics.kt @@ -11,10 +11,4 @@ data class SubjectBiometrics( entityColumn = SUBJECT_ID_COLUMN, ) val biometricTemplates: List, - @Relation( - parentColumn = SUBJECT_ID_COLUMN, - entityColumn = SUBJECT_ID_COLUMN, - ) - /** New field */ - val externalCredentials: List, ) From a3ed7f645ee52312752995d06b2c0482ecbc9f9b Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 25 Aug 2025 18:26:22 +0300 Subject: [PATCH 18/20] [CORE-3421] Addition of the external credentials back in SubjectBiometrics class. Otherwise, it creates more overhead to populate the credentials field --- .../enrolment/records/room/store/models/SubjectBiometrics.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectBiometrics.kt b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectBiometrics.kt index 1ea2f722d2..d35bff3420 100644 --- a/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectBiometrics.kt +++ b/infra/enrolment-records/room-store/src/main/java/com/simprints/infra/enrolment/records/room/store/models/SubjectBiometrics.kt @@ -11,4 +11,9 @@ data class SubjectBiometrics( entityColumn = SUBJECT_ID_COLUMN, ) val biometricTemplates: List, + @Relation( + parentColumn = SUBJECT_ID_COLUMN, + entityColumn = SUBJECT_ID_COLUMN, + ) + val externalCredentials: List, ) From 37d8cb43b07b53425d525c2dc5c7bf3b020149d9 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 26 Aug 2025 10:59:07 +0300 Subject: [PATCH 19/20] [CORE-3421] Fixing tests --- .../ApiEnrolmentRecordCreationEventTest.kt | 10 ++++++---- .../subject/ApiEnrolmentRecordMoveEventTest.kt | 12 ++++++++++++ .../sync/down/tasks/EventDownSyncTaskTest.kt | 15 +++++++++++++++ .../sync/down/tasks/SubjectFactoryTest.kt | 5 +++-- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationEventTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationEventTest.kt index 74fa6fa139..769eee6696 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationEventTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordCreationEventTest.kt @@ -49,10 +49,12 @@ class ApiEnrolmentRecordCreationEventTest { "NEC_1", ), ), - externalCredential = ExternalCredential( - value = "value".asTokenizableEncrypted(), - subjectId = "subjectId", - type = ExternalCredentialType.NHISCard + externalCredentials = listOf( + ExternalCredential( + value = "value".asTokenizableEncrypted(), + subjectId = "subjectId", + type = ExternalCredentialType.NHISCard + ) ), ) diff --git a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordMoveEventTest.kt b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordMoveEventTest.kt index c1ae48e35e..8c13aa8acb 100644 --- a/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordMoveEventTest.kt +++ b/infra/event-sync/src/test/java/com/simprints/infra/eventsync/event/remote/models/subject/ApiEnrolmentRecordMoveEventTest.kt @@ -1,6 +1,8 @@ package com.simprints.infra.eventsync.event.remote.models.subject import com.google.common.truth.Truth.assertThat +import com.simprints.core.domain.externalcredential.ExternalCredential +import com.simprints.core.domain.externalcredential.ExternalCredentialType import com.simprints.core.domain.fingerprint.IFingerIdentifier import com.simprints.core.domain.tokenization.asTokenizableEncrypted import com.simprints.infra.events.event.domain.models.subject.EnrolmentRecordMoveEvent @@ -28,6 +30,11 @@ class ApiEnrolmentRecordMoveEventTest { "NEC_1", ), ), + ApiExternalCredential( + value = "value", + id = "subjectId", + type = ExternalCredentialType.NHISCard.toString() + ) ), ApiEnrolmentRecordMovePayload.ApiEnrolmentRecordDeletionInMove( "subjectId", @@ -51,6 +58,11 @@ class ApiEnrolmentRecordMoveEventTest { "NEC_1", ), ), + ExternalCredential( + value = "value".asTokenizableEncrypted(), + subjectId = "subjectId", + type = ExternalCredentialType.NHISCard + ) ), EnrolmentRecordMoveEvent.EnrolmentRecordDeletionInMove( "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 57b852bfca..5de7881756 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 @@ -81,6 +81,11 @@ class EventDownSyncTaskTest { DEFAULT_MODULE_ID_2, "attendantId".asTokenizableRaw(), listOf(FaceReference("id", listOf(FaceTemplate("template")), "format")), + ExternalCredential( + value = "value".asTokenizableEncrypted(), + subjectId = "subjectId", + type = ExternalCredentialType.NHISCard + ) ), EnrolmentRecordMoveEvent.EnrolmentRecordDeletionInMove( "subjectId", @@ -96,6 +101,11 @@ class EventDownSyncTaskTest { "moduleId".asTokenizableRaw(), DEFAULT_USER_ID, listOf(FaceReference("id", listOf(FaceTemplate("template")), "format")), + ExternalCredential( + value = "value".asTokenizableEncrypted(), + subjectId = "subjectId", + type = ExternalCredentialType.NHISCard + ) ), EnrolmentRecordMoveEvent.EnrolmentRecordDeletionInMove( "subjectId", @@ -111,6 +121,11 @@ class EventDownSyncTaskTest { "moduleId".asTokenizableRaw(), DEFAULT_USER_ID_2, listOf(FaceReference("id", listOf(FaceTemplate("template")), "format")), + ExternalCredential( + value = "value".asTokenizableEncrypted(), + subjectId = "subjectId", + type = ExternalCredentialType.NHISCard + ) ), EnrolmentRecordMoveEvent.EnrolmentRecordDeletionInMove( "subjectId", 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 0b470f6a68..12f17d952f 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 @@ -64,7 +64,7 @@ class SubjectFactoryTest { attendantId = ATTENDANT_ID, moduleId = MODULE_ID, biometricReferences = listOf(FINGERPRINT_REFERENCE, faceReference), - externalCredential = null + externalCredentials = emptyList() ) val result = factory.buildSubjectFromCreationPayload(payload) val expected = Subject( @@ -99,6 +99,7 @@ class SubjectFactoryTest { attendantId = ATTENDANT_ID, moduleId = MODULE_ID, biometricReferences = listOf(FINGERPRINT_REFERENCE, faceReference), + externalCredential = null ) val result = factory.buildSubjectFromMovePayload(payload) @@ -320,7 +321,7 @@ class SubjectFactoryTest { moduleId = expected.moduleId, fingerprintSamples = expected.fingerprintSamples, faceSamples = expected.faceSamples, - externalCredential = expected.externalCredentials.first() + externalCredentials = expected.externalCredentials ) assertThat(result).isEqualTo(expected) } From 05d2a00096a19116cedd2882f04491aaee6cacce Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 26 Aug 2025 14:27:47 +0300 Subject: [PATCH 20/20] [CORE-3421] Adding test coverage for ExternalCredentialType --- .../models/ExternalCredentialTypeTest.kt | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 infra/config-store/src/test/java/com/simprints/infra/config/store/models/ExternalCredentialTypeTest.kt diff --git a/infra/config-store/src/test/java/com/simprints/infra/config/store/models/ExternalCredentialTypeTest.kt b/infra/config-store/src/test/java/com/simprints/infra/config/store/models/ExternalCredentialTypeTest.kt new file mode 100644 index 0000000000..45d8d0e2d4 --- /dev/null +++ b/infra/config-store/src/test/java/com/simprints/infra/config/store/models/ExternalCredentialTypeTest.kt @@ -0,0 +1,43 @@ +package com.simprints.infra.config.store.models + +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import com.simprints.core.domain.externalcredential.ExternalCredentialType +import com.simprints.infra.config.store.exceptions.InvalidProtobufEnumException +import com.simprints.infra.config.store.local.models.ProtoExternalCredentialType +import com.simprints.infra.config.store.local.models.toDomain +import com.simprints.infra.config.store.local.models.toProto +import com.simprints.testtools.common.syntax.assertThrows + +class ExternalCredentialTypeMapperTest { + + @Test + fun `should map correctly the model to proto`() { + val pairs = listOf( + ExternalCredentialType.NHISCard to ProtoExternalCredentialType.NHIS_CARD, + ExternalCredentialType.GhanaIdCard to ProtoExternalCredentialType.GHANA_ID_CARD, + ExternalCredentialType.QRCode to ProtoExternalCredentialType.QR_CODE + ) + + pairs.forEach { (domain, proto) -> + assertThat(proto).isEqualTo(domain.toProto()) + } + } + + @Test + fun `should map correctly the model from proto`() { + val pairs = listOf( + ProtoExternalCredentialType.NHIS_CARD to ExternalCredentialType.NHISCard, + ProtoExternalCredentialType.GHANA_ID_CARD to ExternalCredentialType.GhanaIdCard, + ProtoExternalCredentialType.QR_CODE to ExternalCredentialType.QRCode + ) + + pairs.forEach { (proto, domain) -> + assertThat(domain).isEqualTo(proto.toDomain()) + } + + assertThrows { + ProtoExternalCredentialType.UNRECOGNIZED.toDomain() + } + } +}