Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d53317d
[MS-1065] Adding 'metadata' field to the ConfirmationCalloutEventV3ˆ
alexandr-simprints Jul 14, 2025
a6aaf46
[MS-1065] Adding 'metadata' field to the ConfirmationCalloutEventV3ˆ
alexandr-simprints Jul 14, 2025
e250e60
[MS-1065] Making 'metadata' field nullable inˆ the ConfirmationCallou…
alexandr-simprints Jul 14, 2025
ed29476
Merge pull request #1262 from Simprints/MS-1065-android-add-metadata-…
alexandr-simprints Jul 14, 2025
03d023e
Disable file log writer on per-oreo Android versions
luhmirin-s Jul 15, 2025
844168c
Merge pull request #1264 from Simprints/hotfix/log-writer-fix
luhmirin-s Jul 15, 2025
a62630e
[MS-1069] update troubleshooting migration screen a logic to check us…
meladRaouf Jul 15, 2025
245e8f2
Merge pull request #1265 from Simprints/hide-migration-tab
meladRaouf Jul 15, 2025
9442c87
Do not keep sessions if project is paused or ended
luhmirin-s Jul 16, 2025
50e6eda
Merge pull request #1267 from Simprints/hotfix/delete-session-fix
luhmirin-s Jul 17, 2025
2ccc58f
Merge remote-tracking branch 'origin/release/2025.2.0' into release/2…
alexandr-simprints Jul 17, 2025
0b54382
MS-1077 Only return templates of the correct format for matching
luhmirin-s Jul 17, 2025
fef39e7
Merge pull request #1268 from Simprints/hotfix/MS-1077-template-query…
luhmirin-s Jul 17, 2025
c25fd06
[MS-1076] Changing metadata field from `HashMap` to a generic `Map` t…
alexandr-simprints Jul 21, 2025
4c36e2b
[MS-1076] Adding descriptive comments for future reference
alexandr-simprints Jul 21, 2025
c76f49e
[MS-1076] Marking API data classes with @ExcludedFromGeneratedTestCov…
alexandr-simprints Jul 21, 2025
0f98fea
[MS-1076] Marking domain model classes with @ExcludedFromGeneratedTes…
alexandr-simprints Jul 21, 2025
e0165f6
Merge pull request #1275 from Simprints/MS-1076-sid-is-not-handling-t…
alexandr-simprints Jul 22, 2025
d27ee82
Translate strings.xml in bn
transifex-integration[bot] Jul 22, 2025
5009f79
Translate strings.xml in hi
transifex-integration[bot] Jul 22, 2025
dc92f31
Translate strings.xml in am_ET
transifex-integration[bot] Jul 22, 2025
f9c6d6c
Translate strings.xml in am
transifex-integration[bot] Jul 22, 2025
e708b65
Translate strings.xml in fr
transifex-integration[bot] Jul 22, 2025
e36faed
Merge pull request #1278 from Simprints/hotfix/translations-backport
luhmirin-s Jul 22, 2025
8242d56
Merge branch 'release/2025.2.0' into merge/2025.2.0
luhmirin-s Jul 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.simprints.feature.clientapi.usecases

import com.simprints.core.SessionCoroutineScope
import com.simprints.infra.authstore.AuthStore
import com.simprints.infra.config.store.models.ProjectState
import com.simprints.infra.config.store.models.canSyncDataToSimprints
import com.simprints.infra.config.sync.ConfigManager
import com.simprints.infra.events.EventRepository
Expand All @@ -9,12 +11,16 @@ import kotlinx.coroutines.launch
import javax.inject.Inject

internal class DeleteSessionEventsIfNeededUseCase @Inject constructor(
private val authStore: AuthStore,
private val configManager: ConfigManager,
private val eventRepository: EventRepository,
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
) {
operator fun invoke(sessionId: String) = sessionCoroutineScope.launch {
if (!configManager.getProjectConfiguration().canSyncDataToSimprints()) {
val projectNotRunning = configManager.getProject(authStore.signedInProjectId).state != ProjectState.RUNNING
val simprintsDataSyncDisabled = !configManager.getProjectConfiguration().canSyncDataToSimprints()

if (simprintsDataSyncDisabled || projectNotRunning) {
eventRepository.deleteEventScope(sessionId)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package com.simprints.feature.clientapi.usecases

import com.simprints.infra.authstore.AuthStore
import com.simprints.infra.config.store.models.ProjectState
import com.simprints.infra.config.store.models.UpSynchronizationConfiguration
import com.simprints.infra.config.sync.ConfigManager
import com.simprints.infra.events.EventRepository
import com.simprints.testtools.common.coroutines.TestCoroutineRule
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.mockk
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
Expand All @@ -19,6 +21,9 @@ class DeleteSessionEventsIfNeededUseCaseTest {
@get:Rule
val testCoroutineRule = TestCoroutineRule()

@MockK
private lateinit var authStore: AuthStore

@MockK
private lateinit var configManager: ConfigManager

Expand All @@ -31,29 +36,80 @@ class DeleteSessionEventsIfNeededUseCaseTest {
fun setUp() {
MockKAnnotations.init(this, relaxed = true)

every { authStore.signedInProjectId } returns "projectId"

deleteUseCase = DeleteSessionEventsIfNeededUseCase(
authStore,
configManager,
eventRepository,
CoroutineScope(testCoroutineRule.testCoroutineDispatcher),
)
}

@Test
fun `deletes session events if data sync disabled`() = runTest {
coEvery { configManager.getProjectConfiguration() } returns mockk {
coEvery { synchronization.up.simprints.kind } returns UpSynchronizationConfiguration.UpSynchronizationKind.NONE
}
fun `deletes session events if project paused`() = runTest {
coEvery { configManager.getProject(any()).state } returns ProjectState.PROJECT_PAUSED
coEvery {
configManager
.getProjectConfiguration()
.synchronization.up.simprints.kind
} returns UpSynchronizationConfiguration.UpSynchronizationKind.ALL

deleteUseCase("sessionId")

coVerify { eventRepository.deleteEventScope("sessionId") }
}

@Test
fun `deletes session events if project ending`() = runTest {
coEvery { configManager.getProject(any()).state } returns ProjectState.PROJECT_ENDING
coEvery {
configManager
.getProjectConfiguration()
.synchronization.up.simprints.kind
} returns UpSynchronizationConfiguration.UpSynchronizationKind.ALL

deleteUseCase("sessionId")

coVerify { eventRepository.deleteEventScope("sessionId") }
}

@Test
fun `deletes session events if project ended`() = runTest {
coEvery { configManager.getProject(any()).state } returns ProjectState.PROJECT_ENDED
coEvery {
configManager
.getProjectConfiguration()
.synchronization.up.simprints.kind
} returns UpSynchronizationConfiguration.UpSynchronizationKind.ALL

deleteUseCase("sessionId")

coVerify { eventRepository.deleteEventScope("sessionId") }
}

@Test
fun `deletes session events if data sync disabled in running project`() = runTest {
coEvery { configManager.getProject(any()).state } returns ProjectState.RUNNING
coEvery {
configManager
.getProjectConfiguration()
.synchronization.up.simprints.kind
} returns UpSynchronizationConfiguration.UpSynchronizationKind.NONE

deleteUseCase("sessionId")

coVerify { eventRepository.deleteEventScope("sessionId") }
}

@Test
fun `does not delete session events if data sync enabled`() = runTest {
coEvery { configManager.getProjectConfiguration() } returns mockk {
coEvery { synchronization.up.simprints.kind } returns UpSynchronizationConfiguration.UpSynchronizationKind.ALL
}
fun `does not delete session events if data sync enabled in running project`() = runTest {
coEvery { configManager.getProject(any()).state } returns ProjectState.RUNNING
coEvery {
configManager
.getProjectConfiguration()
.synchronization.up.simprints.kind
} returns UpSynchronizationConfiguration.UpSynchronizationKind.ALL

deleteUseCase("sessionId")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,13 @@ internal class ReportActionRequestEventsUseCase @Inject constructor(
metadata,
BiometricDataSource.fromString(biometricDataSource),
)
is ActionRequest.ConfirmIdentityActionRequest -> ConfirmationCalloutEventV3(startTime, projectId, selectedGuid, sessionId)
is ActionRequest.ConfirmIdentityActionRequest -> ConfirmationCalloutEventV3(
startTime,
projectId,
selectedGuid,
sessionId,
metadata,
)
is ActionRequest.EnrolLastBiometricActionRequest -> EnrolmentLastBiometricsCalloutEventV3(
startTime,
projectId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.simprints.feature.troubleshooting.overview

import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.core.content.FileProvider
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.simprints.feature.troubleshooting.R
Expand Down Expand Up @@ -68,6 +70,9 @@ internal class OverviewFragment : Fragment(R.layout.fragment_troubleshooting_ove
binding.troubleshootOverviewPing.setOnClickListener {
viewModel.pingServer()
}

// Log export is only Available starting from Android 8
binding.troubleshootOverviewExportLogsBlock.isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
binding.troubleshootOverviewExportLogs.setOnClickListener { v ->
viewModel.exportLogs()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.simprints.feature.troubleshooting.adapter.TroubleshootingItemViewData
import com.simprints.infra.authstore.AuthStore
import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository
import com.simprints.infra.enrolment.records.repository.local.migration.RealmToRoomMigrationFlagsStore
import dagger.hilt.android.lifecycle.HiltViewModel
Expand All @@ -15,6 +16,7 @@ import javax.inject.Inject
internal class RealmToRoomRecordsMigrationViewModel @Inject constructor(
private val flagStore: RealmToRoomMigrationFlagsStore,
private val enrolmentRecordRepository: EnrolmentRecordRepository,
private val authStore: AuthStore,
) : ViewModel() {
private val _logs = MutableLiveData<List<TroubleshootingItemViewData>>(emptyList())
val logs: LiveData<List<TroubleshootingItemViewData>>
Expand All @@ -30,7 +32,11 @@ internal class RealmToRoomRecordsMigrationViewModel @Inject constructor(
),
TroubleshootingItemViewData(
title = "Local db info",
body = enrolmentRecordRepository.getLocalDBInfo(),
body = if (authStore.signedInProjectId.isNotEmpty()) {
enrolmentRecordRepository.getLocalDBInfo()
} else {
"No local db info available for logged out users."
},
),
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
android:textIsSelectable="true" />

<LinearLayout
android:id="@+id/troubleshootOverviewExportLogsBlock"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="16dp"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.simprints.feature.troubleshooting.reordsmigration

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.*
import com.simprints.feature.troubleshooting.recordsmigration.RealmToRoomRecordsMigrationViewModel
import com.simprints.infra.authstore.AuthStore
import com.simprints.infra.enrolment.records.repository.EnrolmentRecordRepository
import com.simprints.infra.enrolment.records.repository.local.migration.RealmToRoomMigrationFlagsStore
import com.simprints.testtools.common.coroutines.TestCoroutineRule
import com.simprints.testtools.common.livedata.getOrAwaitValue
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.impl.annotations.*
import io.mockk.*
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
Expand All @@ -28,12 +28,15 @@ class RealmToRoomRecordsMigrationViewModelTest {
@MockK
private lateinit var enrolmentRecordRepository: EnrolmentRecordRepository

@MockK
private lateinit var authStore: AuthStore

private lateinit var viewModel: RealmToRoomRecordsMigrationViewModel

@Before
fun setup() {
MockKAnnotations.init(this, relaxed = true)
viewModel = RealmToRoomRecordsMigrationViewModel(flagStore, enrolmentRecordRepository)
viewModel = RealmToRoomRecordsMigrationViewModel(flagStore, enrolmentRecordRepository, authStore)
}

@Test
Expand All @@ -42,6 +45,7 @@ class RealmToRoomRecordsMigrationViewModelTest {
val mockFlagState = "flags: ENABLED"
val mockDbInfo = "Database Version: 1"

every { authStore.signedInProjectId } returns "project_id"
coEvery { flagStore.getStoreStateAsString() } returns mockFlagState
coEvery { enrolmentRecordRepository.getLocalDBInfo() } returns mockDbInfo

Expand All @@ -58,4 +62,29 @@ class RealmToRoomRecordsMigrationViewModelTest {
assertThat(logs[1].title).isEqualTo("Local db info")
assertThat(logs[1].body).isEqualTo(mockDbInfo)
}

@Test
fun `collectData should post no local DB info when user is logged out`() = runTest {
// Given
val mockFlagState = "flags: ENABLED"
val mockDbInfo = "Database Version: 1"

every { authStore.signedInProjectId } returns ""
coEvery { flagStore.getStoreStateAsString() } returns mockFlagState
coEvery { enrolmentRecordRepository.getLocalDBInfo() } returns mockDbInfo

// When
viewModel.collectData()

// Then
val logs = viewModel.logs.getOrAwaitValue()

assertThat(logs).hasSize(2)
assertThat(logs[0].title).isEqualTo("Realm to Room migration flags:")
assertThat(logs[0].body).isEqualTo(mockFlagState)

assertThat(logs[1].title).isEqualTo("Local db info")
assertThat(logs[1].body).isEqualTo("No local db info available for logged out users.")
coVerify(exactly = 0) { enrolmentRecordRepository.getLocalDBInfo() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ internal class RealmEnrolmentRecordLocalDataSource @Inject constructor(
mapper = { dbSubject ->
FaceIdentity(
subjectId = dbSubject.subjectId.toString(),
faces = dbSubject.faceSamples.map { it.toDomain() },
faces = dbSubject.faceSamples
.filter { it.format == query.faceSampleFormat }
.map { it.toDomain() },
)
},
onCandidateLoaded = onCandidateLoaded,
Expand Down Expand Up @@ -109,7 +111,9 @@ internal class RealmEnrolmentRecordLocalDataSource @Inject constructor(
mapper = { dbSubject ->
FingerprintIdentity(
subjectId = dbSubject.subjectId.toString(),
fingerprints = dbSubject.fingerprintSamples.map { it.toDomain() },
fingerprints = dbSubject.fingerprintSamples
.filter { it.format == query.fingerprintSampleFormat }
.map { it.toDomain() },
)
},
onCandidateLoaded = onCandidateLoaded,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package com.simprints.infra.enrolment.records.repository.remote.models.face

import androidx.annotation.Keep
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.simprints.core.ExcludedFromGeneratedTestCoverageReports
import com.simprints.core.domain.face.FaceSample
import com.simprints.core.tools.utils.EncodingUtils
import com.simprints.infra.enrolment.records.repository.remote.models.ApiBiometricReference

@Keep
@ExcludedFromGeneratedTestCoverageReports("API model")
internal data class ApiFaceReference(
val id: String,
val templates: List<ApiFaceTemplate>,
val format: String,
// [MS-1076] The parent 'ApiBiometricReference' class should have its JsonSubTypes annotation updated to
// @JsonSubTypes.Type([...], looseHandling = true) once we update to SDK => 25 and Jackson => 2.16.0.
// Then, this annotation should be removed
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
val metadata: HashMap<String, String>? = null,
) : ApiBiometricReference(ApiBiometricReferenceType.FaceReference)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package com.simprints.infra.enrolment.records.repository.remote.models.fingerprint

import androidx.annotation.Keep
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.simprints.core.ExcludedFromGeneratedTestCoverageReports
import com.simprints.core.domain.fingerprint.FingerprintSample
import com.simprints.core.tools.utils.EncodingUtils
import com.simprints.infra.enrolment.records.repository.remote.models.ApiBiometricReference

@Keep
@ExcludedFromGeneratedTestCoverageReports("API model")
internal data class ApiFingerprintReference(
val id: String,
val templates: List<ApiFingerprintTemplate>,
val format: String,
// [MS-1076] The parent 'ApiBiometricReference' class should have its JsonSubTypes annotation updated to
// @JsonSubTypes.Type([...], looseHandling = true) once we update to SDK => 25 and Jackson => 2.16.0.
// Then, this annotation should be removed
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
val metadata: HashMap<String, String>? = null,
) : ApiBiometricReference(ApiBiometricReferenceType.FingerprintReference)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ internal data class ApiCalloutPayloadV2(
ApiConfirmationCalloutV2(
domainPayload.selectedGuid,
domainPayload.sessionId,
domainPayload.metadata,
),
)

Expand Down
Loading
Loading