From 904aea087fc90a88d7bf3b92b63173e860bec8ff Mon Sep 17 00:00:00 2001 From: Melad Raouf Date: Wed, 30 Jul 2025 06:55:37 +0300 Subject: [PATCH] [MS-1093] Implement bulk session event generation and insertion logic --- .../simprints/infra/events/EventRepository.kt | 8 + .../infra/events/EventRepositoryImpl.kt | 3 + .../event/local/EventLocalDataSource.kt | 18 ++ testing/data-generator/README.md | 73 ++++-- .../assets/dummy_events/AUTHORIZATION.sql | 13 + .../BIOMETRIC_REFERENCE_CREATION.sql | 13 + .../dummy_events/CALLBACK_CONFIRMATION.sql | 13 + .../dummy_events/CALLBACK_ENROLMENT.sql | 13 + .../dummy_events/CALLBACK_IDENTIFICATION.sql | 13 + .../dummy_events/CALLBACK_VERIFICATION.sql | 13 + .../dummy_events/CALLOUT_CONFIRMATION.sql | 13 + .../assets/dummy_events/CALLOUT_ENROLMENT.sql | 13 + .../dummy_events/CALLOUT_IDENTIFICATION.sql | 13 + .../dummy_events/CALLOUT_VERIFICATION.sql | 13 + .../assets/dummy_events/CANDIDATE_READ.sql | 13 + .../assets/dummy_events/COMPLETION_CHECK.sql | 13 + .../dummy_events/CONNECTIVITY_SNAPSHOT.sql | 13 + .../src/debug/assets/dummy_events/CONSENT.sql | 13 + .../debug/assets/dummy_events/ENROLMENT.sql | 13 + .../assets/dummy_events/FACE_CAPTURE.sql | 13 + .../dummy_events/FACE_CAPTURE_BIOMETRICS.sql | 13 + .../FACE_CAPTURE_CONFIRMATION.sql | 13 + .../dummy_events/FACE_FALLBACK_CAPTURE.sql | 13 + .../dummy_events/FINGERPRINT_CAPTURE.sql | 13 + .../FINGERPRINT_CAPTURE_BIOMETRICS.sql | 13 + .../assets/dummy_events/GUID_SELECTION.sql | 13 + .../assets/dummy_events/INTENT_PARSING.sql | 13 + .../assets/dummy_events/LICENSE_CHECK.sql | 13 + .../assets/dummy_events/ONE_TO_MANY_MATCH.sql | 13 + .../assets/dummy_events/ONE_TO_ONE_MATCH.sql | 13 + .../dummy_events/SCANNER_CONNECTION.sql | 13 + .../dummy_events/VERO_2_INFO_SNAPSHOT.sql | 13 + .../datagenerator/DataGeneratorViewModel.kt | 17 +- .../events/InsertSessionEventsUseCase.kt | 168 ++++++++++++ .../datagenerator/events/SessionGenerator.kt | 244 ++++++++++++++++++ .../events/SqlEventTemplateLoader.kt | 66 +++++ .../DataGeneratorViewModelTest.kt | 66 ++++- .../events/InsertSessionEventsUseCaseTest.kt | 117 +++++++++ .../events/SessionGeneratorTest.kt | 118 +++++++++ .../events/SqlEventTemplateLoaderTest.kt | 140 ++++++++++ 40 files changed, 1382 insertions(+), 20 deletions(-) create mode 100644 testing/data-generator/src/debug/assets/dummy_events/AUTHORIZATION.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/BIOMETRIC_REFERENCE_CREATION.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/CALLBACK_CONFIRMATION.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/CALLBACK_ENROLMENT.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/CALLBACK_IDENTIFICATION.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/CALLBACK_VERIFICATION.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/CALLOUT_CONFIRMATION.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/CALLOUT_ENROLMENT.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/CALLOUT_IDENTIFICATION.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/CALLOUT_VERIFICATION.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/CANDIDATE_READ.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/COMPLETION_CHECK.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/CONNECTIVITY_SNAPSHOT.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/CONSENT.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/ENROLMENT.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/FACE_CAPTURE.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/FACE_CAPTURE_BIOMETRICS.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/FACE_CAPTURE_CONFIRMATION.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/FACE_FALLBACK_CAPTURE.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/FINGERPRINT_CAPTURE.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/FINGERPRINT_CAPTURE_BIOMETRICS.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/GUID_SELECTION.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/INTENT_PARSING.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/LICENSE_CHECK.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/ONE_TO_MANY_MATCH.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/ONE_TO_ONE_MATCH.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/SCANNER_CONNECTION.sql create mode 100644 testing/data-generator/src/debug/assets/dummy_events/VERO_2_INFO_SNAPSHOT.sql create mode 100644 testing/data-generator/src/main/java/com/simprints/feature/datagenerator/events/InsertSessionEventsUseCase.kt create mode 100644 testing/data-generator/src/main/java/com/simprints/feature/datagenerator/events/SessionGenerator.kt create mode 100644 testing/data-generator/src/main/java/com/simprints/feature/datagenerator/events/SqlEventTemplateLoader.kt create mode 100644 testing/data-generator/src/test/java/com/simprints/feature/datagenerator/events/InsertSessionEventsUseCaseTest.kt create mode 100644 testing/data-generator/src/test/java/com/simprints/feature/datagenerator/events/SessionGeneratorTest.kt create mode 100644 testing/data-generator/src/test/java/com/simprints/feature/datagenerator/events/SqlEventTemplateLoaderTest.kt diff --git a/infra/events/src/main/java/com/simprints/infra/events/EventRepository.kt b/infra/events/src/main/java/com/simprints/infra/events/EventRepository.kt index b455717ae3..874a6757ba 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/EventRepository.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/EventRepository.kt @@ -66,4 +66,12 @@ interface EventRepository { ): Event suspend fun deleteAll() + + /** + * Uses raw SQL insertion commands to insert events into the database. + * This is useful for bulk inserts or when the events are pre-formatted as SQL commands + * Only use this method in debugging or testing scenarios where you have control over the SQL commands. + * it throws an exception if called in production code. + */ + suspend fun executeRawEventInsertions(rawSqlInsertStatements: List) } diff --git a/infra/events/src/main/java/com/simprints/infra/events/EventRepositoryImpl.kt b/infra/events/src/main/java/com/simprints/infra/events/EventRepositoryImpl.kt index 1e445167c3..e184fdade9 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/EventRepositoryImpl.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/EventRepositoryImpl.kt @@ -207,4 +207,7 @@ internal open class EventRepositoryImpl @Inject constructor( throw t } + + override suspend fun executeRawEventInsertions(rawSqlInsertStatements: List) = + eventLocalDataSource.executeRawEventInsertions(rawSqlInsertStatements) } diff --git a/infra/events/src/main/java/com/simprints/infra/events/event/local/EventLocalDataSource.kt b/infra/events/src/main/java/com/simprints/infra/events/event/local/EventLocalDataSource.kt index 0d6a6fa14a..faf2cf5a5c 100644 --- a/infra/events/src/main/java/com/simprints/infra/events/event/local/EventLocalDataSource.kt +++ b/infra/events/src/main/java/com/simprints/infra/events/event/local/EventLocalDataSource.kt @@ -5,6 +5,7 @@ import android.database.sqlite.SQLiteException import com.simprints.core.DispatcherIO import com.simprints.core.NonCancellableIO import com.simprints.core.tools.json.JsonHelper +import com.simprints.infra.events.BuildConfig import com.simprints.infra.events.event.domain.models.Event import com.simprints.infra.events.event.domain.models.EventType import com.simprints.infra.events.event.domain.models.scope.EventScope @@ -174,6 +175,23 @@ internal open class EventLocalDataSource @Inject constructor( eventDao.deleteAll() } + suspend fun executeRawEventInsertions(rawSqlInsertStatements: List) = useRoom(writingContext) { + // this method should only be used in debug builds for testing purposes + check(BuildConfig.DEBUG) { "executeRawEventInsertions should only be used in debug builds for testing purposes" } + + val sqliteDb = eventDatabaseFactory.get().openHelper.writableDatabase + sqliteDb.beginTransaction() + try { + rawSqlInsertStatements.forEach { + sqliteDb.execSQL(it) + } + } catch (e: Exception) { + e.printStackTrace() + } + sqliteDb.setTransactionSuccessful() + sqliteDb.endTransaction() + } + companion object { // Actual limit is 999, but it is better to leave some wiggle room private const val SQLITE_VARIABLE_LIMIT = 900 diff --git a/testing/data-generator/README.md b/testing/data-generator/README.md index 3c753c04ad..e1d0f5126c 100644 --- a/testing/data-generator/README.md +++ b/testing/data-generator/README.md @@ -1,15 +1,36 @@ # 📦 Debug-Only Biometric Data Generator -This module allows developers to insert **bulk biometric enrollment records** directly into the local SID database for **developer testing**, **performance testing**, and **E2E test setup**. It is only available in **debug builds** and should never be shipped in production. +This module enables developers to insert **bulk biometric enrollment records** and **session events** into the local SID database for **testing and E2E setup**. +**Debug builds only** — never ship in production. --- -## 🚀 How to Use +## Table of Contents -Use the following ADB command to trigger bulk record generation via an explicit `Intent`. This will insert fingerprint and face templates using pre-configured logic. +- [Features](#features) +- [Usage](#usage) + - [Biometric Records Generation](#biometric-records-generation) + - [Session Event Generation](#session-event-generation) +- [Parameters](#parameters) +- [Face Template Image](#face-template-image) -### ✅ Example Command +--- + +## Features + +- Bulk insert of biometric enrollment records (fingerprint, face) +- Bulk generation of session events (enrol, identify, verify, etc.) +- Designed for performance, E2E, and sync testing + +--- + +## Usage +### Biometric Records Generation + +Populate the database with many enrollment records for 1:1 and 1:N biometric matching tests. + +**Command:** ```bash adb shell am start \ -a com.simprints.test.GENERATE_ENROLLMENT_RECORDS \ @@ -22,10 +43,7 @@ adb shell am start \ --es EXTRA_FINGER_ORDER.NEC_1 "RIGHT_INDEX_FINGER,LEFT_THUMB" \ --es EXTRA_FIRST_SUBJECT_ID "d9a6c3f7-a6c3-d9a6-c3f7-a6c3d9a6c3f7" ``` - ---- - -## 🧠 Parameters +### Parameters | Key | Description | | ------------------------------ |----------------------------------------------| @@ -37,13 +55,34 @@ adb shell am start \ | `EXTRA_FINGER_ORDER.*` | Comma-separated finger order for each format | | `EXTRA_FIRST_SUBJECT_ID` | UUID of the first subject | ---- - -## 🖼️ Face Template Image - -This generator uses a static face image to create biometric templates. -📍![Face image](./FACE-IMAGE.png) - - +### Session Event Generation +Simulate real-world usage and server sync by generating session events. +**Command:** +```bash +adb shell am start \ + -a com.simprints.test.GENERATE_SESSION_EVENTS \ + --es EXTRA_PROJECT_ID "oPru9XTAI2hE2nDFD5vZ" \ + --es EXTRA_MODULE_ID "module-abc" \ + --es EXTRA_ATTENDANT_ID "user-xyz" \ + --ei EXTRA_ENROL_COUNT 2 \ + --ei EXTRA_IDENTIFY_COUNT 2 \ + --ei EXTRA_VERIFY_COUNT 2 \ + --ei EXTRA_CONFIRM_IDENTIFY_COUNT 2 \ + --ei EXTRA_ENROL_LAST_COUNT 2 +``` +### Parameters +| Key | Type | Description | +|-----------------------------|--------|-----------------------------------------------------------------------| +| `EXTRA_PROJECT_ID` | String | The ID of the project to associate with the generated session events. | +| `EXTRA_MODULE_ID` | String | The module identifier | +| `EXTRA_ATTENDANT_ID` | String | The ID of the field worker | +| `EXTRA_ENROL_COUNT` | Int | Number of enrolment sessions generate. | +| `EXTRA_IDENTIFY_COUNT` | Int | Number of identification sessions to generate. | +| `EXTRA_VERIFY_COUNT` | Int | Number of verification sessions to generate. | +| `EXTRA_CONFIRM_IDENTIFY_COUNT` | Int | Number of confirm-identification sessions to generate. | +| `EXTRA_ENROL_LAST_COUNT` | Int | Number of last enrolment sessions to generate . | ---- +### Face Template Image +Face Template Image +A static face image is used for template generation: +📍Face image diff --git a/testing/data-generator/src/debug/assets/dummy_events/AUTHORIZATION.sql b/testing/data-generator/src/debug/assets/dummy_events/AUTHORIZATION.sql new file mode 100644 index 0000000000..2d67e5d043 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/AUTHORIZATION.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'AUTHORIZATION', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":2,"result":"AUTHORIZED","userInfo":{"projectId":"__project_id__","userId":{"className":"TokenizableString.Raw","value":"__module_id__"}},"type":"AUTHORIZATION"},"type":"AUTHORIZATION","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/BIOMETRIC_REFERENCE_CREATION.sql b/testing/data-generator/src/debug/assets/dummy_events/BIOMETRIC_REFERENCE_CREATION.sql new file mode 100644 index 0000000000..71b974d5e7 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/BIOMETRIC_REFERENCE_CREATION.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'BIOMETRIC_REFERENCE_CREATION', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":1,"id":"__event_id__","modality":"FACE","captureIds":["872271c4-695d-495d-910a-efde23097f8a","6ed2bfa5-d076-40ee-af33-ce7f4c826e23"],"type":"BIOMETRIC_REFERENCE_CREATION"},"type":"BIOMETRIC_REFERENCE_CREATION","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/CALLBACK_CONFIRMATION.sql b/testing/data-generator/src/debug/assets/dummy_events/CALLBACK_CONFIRMATION.sql new file mode 100644 index 0000000000..e5ba55639b --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/CALLBACK_CONFIRMATION.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'CALLBACK_CONFIRMATION', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":3,"identificationOutcome":true,"type":"CALLBACK_CONFIRMATION"},"type":"CALLBACK_CONFIRMATION","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/CALLBACK_ENROLMENT.sql b/testing/data-generator/src/debug/assets/dummy_events/CALLBACK_ENROLMENT.sql new file mode 100644 index 0000000000..c0ba39d9e3 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/CALLBACK_ENROLMENT.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'CALLBACK_ENROLMENT', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":3,"guid":"872271c4-695d-495d-910a-efde23097f8a","type":"CALLBACK_ENROLMENT"},"type":"CALLBACK_ENROLMENT","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/CALLBACK_IDENTIFICATION.sql b/testing/data-generator/src/debug/assets/dummy_events/CALLBACK_IDENTIFICATION.sql new file mode 100644 index 0000000000..175740d0d5 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/CALLBACK_IDENTIFICATION.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'CALLBACK_IDENTIFICATION', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":3,"sessionId":"872271c4-695d-495d-910a-efde23097f8a","scores":[{"guid":"872271c4-695d-495d-910a-efde23097f8a","confidence":1,"confidenceMatch":"MEDIUM"}],"type":"CALLBACK_IDENTIFICATION"},"type":"CALLBACK_IDENTIFICATION","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/CALLBACK_VERIFICATION.sql b/testing/data-generator/src/debug/assets/dummy_events/CALLBACK_VERIFICATION.sql new file mode 100644 index 0000000000..a9f6df80a5 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/CALLBACK_VERIFICATION.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'CALLBACK_VERIFICATION', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":3,"score":{"guid":"872271c4-695d-495d-910a-efde23097f8a","confidence":1,"confidenceMatch":"MEDIUM"},"type":"CALLBACK_VERIFICATION"},"type":"CALLBACK_VERIFICATION","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/CALLOUT_CONFIRMATION.sql b/testing/data-generator/src/debug/assets/dummy_events/CALLOUT_CONFIRMATION.sql new file mode 100644 index 0000000000..403cec4f39 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/CALLOUT_CONFIRMATION.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'CALLOUT_CONFIRMATION_V3', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":3,"projectId":"__project_id__","selectedGuid":"872271c4-695d-495d-910a-efde23097f8a","sessionId":"6ed2bfa5-d076-40ee-af33-ce7f4c826e23","metadata":"DEFAULT_METADATA","type":"CALLOUT_CONFIRMATION_V3"},"type":"CALLOUT_CONFIRMATION_V3","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/CALLOUT_ENROLMENT.sql b/testing/data-generator/src/debug/assets/dummy_events/CALLOUT_ENROLMENT.sql new file mode 100644 index 0000000000..6cbbe27941 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/CALLOUT_ENROLMENT.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'CALLOUT_ENROLMENT_V3', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":3,"projectId":"__project_id__","userId":{"className":"TokenizableString.Raw","value":"__module_id__"},"moduleId":{"className":"TokenizableString.Raw","value":"user-xyz"},"metadata":"DEFAULT_METADATA","biometricDataSource":"SIMPRINTS","type":"CALLOUT_ENROLMENT_V3"},"type":"CALLOUT_ENROLMENT_V3","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/CALLOUT_IDENTIFICATION.sql b/testing/data-generator/src/debug/assets/dummy_events/CALLOUT_IDENTIFICATION.sql new file mode 100644 index 0000000000..ac7d436123 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/CALLOUT_IDENTIFICATION.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'CALLOUT_IDENTIFICATION_V3', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":3,"projectId":"__project_id__","userId":{"className":"TokenizableString.Raw","value":"__module_id__"},"moduleId":{"className":"TokenizableString.Raw","value":"user-xyz"},"metadata":"DEFAULT_METADATA","biometricDataSource":"SIMPRINTS","type":"CALLOUT_IDENTIFICATION_V3"},"type":"CALLOUT_IDENTIFICATION_V3","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/CALLOUT_VERIFICATION.sql b/testing/data-generator/src/debug/assets/dummy_events/CALLOUT_VERIFICATION.sql new file mode 100644 index 0000000000..1a6cfbc85e --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/CALLOUT_VERIFICATION.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'CALLOUT_VERIFICATION_V3', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":3,"projectId":"__project_id__","userId":{"className":"TokenizableString.Raw","value":"__attended_id__"},"moduleId":{"className":"TokenizableString.Raw","value":"__module_id__"},"verifyGuid":"6ed2bfa5-d076-40ee-af33-ce7f4c826e23","metadata":"DEFAULT_METADATA","biometricDataSource":"SIMPRINTS","type":"CALLOUT_VERIFICATION_V3"},"type":"CALLOUT_VERIFICATION_V3","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/CANDIDATE_READ.sql b/testing/data-generator/src/debug/assets/dummy_events/CANDIDATE_READ.sql new file mode 100644 index 0000000000..b15193285b --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/CANDIDATE_READ.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'CANDIDATE_READ', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":2,"candidateId":"872271c4-695d-495d-910a-efde23097f8a","localResult":"FOUND","remoteResult":"NOT_FOUND","endedAt":{"ms":1754153597998,"isTrustworthy":false},"type":"CANDIDATE_READ"},"type":"CANDIDATE_READ","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/COMPLETION_CHECK.sql b/testing/data-generator/src/debug/assets/dummy_events/COMPLETION_CHECK.sql new file mode 100644 index 0000000000..9902a37f18 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/COMPLETION_CHECK.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'COMPLETION_CHECK', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":2,"completed":true,"type":"COMPLETION_CHECK"},"type":"COMPLETION_CHECK","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/CONNECTIVITY_SNAPSHOT.sql b/testing/data-generator/src/debug/assets/dummy_events/CONNECTIVITY_SNAPSHOT.sql new file mode 100644 index 0000000000..17be537feb --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/CONNECTIVITY_SNAPSHOT.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'CONNECTIVITY_SNAPSHOT', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":3,"connections":[{"type":"MOBILE","state":"CONNECTED"}],"type":"CONNECTIVITY_SNAPSHOT"},"type":"CONNECTIVITY_SNAPSHOT","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/CONSENT.sql b/testing/data-generator/src/debug/assets/dummy_events/CONSENT.sql new file mode 100644 index 0000000000..b4cce21d6d --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/CONSENT.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'CONSENT', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":2,"endedAt":{"ms":1754153597998,"isTrustworthy":false},"consentType":"INDIVIDUAL","result":"ACCEPTED","type":"CONSENT"},"type":"CONSENT","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/ENROLMENT.sql b/testing/data-generator/src/debug/assets/dummy_events/ENROLMENT.sql new file mode 100644 index 0000000000..5004e48dba --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/ENROLMENT.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'ENROLMENT_V4', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":4,"subjectId":"872271c4-695d-495d-910a-efde23097f8a","projectId":"__project_id__","moduleId":{"className":"TokenizableString.Raw","value":"__module_id__"},"attendantId":{"className":"TokenizableString.Raw","value":"user-xyz"},"biometricReferenceIds":["872271c4-695d-495d-910a-efde23097f8a","6ed2bfa5-d076-40ee-af33-ce7f4c826e23"],"type":"ENROLMENT_V4"},"type":"ENROLMENT_V4","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/FACE_CAPTURE.sql b/testing/data-generator/src/debug/assets/dummy_events/FACE_CAPTURE.sql new file mode 100644 index 0000000000..c6da04f176 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/FACE_CAPTURE.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'FACE_CAPTURE', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"id":"__event_id__","createdAt":{"ms":1754153597998,"isTrustworthy":false},"endedAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":4,"attemptNb":0,"qualityThreshold":1.0,"result":"VALID","isAutoCapture":false,"isFallback":true,"face":{"yaw":0.0,"roll":1.0,"quality":2.0,"format":"RANK_ONE_1_23"},"type":"FACE_CAPTURE"},"type":"FACE_CAPTURE","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/FACE_CAPTURE_BIOMETRICS.sql b/testing/data-generator/src/debug/assets/dummy_events/FACE_CAPTURE_BIOMETRICS.sql new file mode 100644 index 0000000000..614c715ebb --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/FACE_CAPTURE_BIOMETRICS.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'FACE_CAPTURE_BIOMETRICS', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"id":"__event_id__","createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":1,"face":{"yaw":1.0,"roll":0.0,"template":"template","quality":1.0,"format":"RANK_ONE_1_23"},"type":"FACE_CAPTURE_BIOMETRICS"},"type":"FACE_CAPTURE_BIOMETRICS","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/FACE_CAPTURE_CONFIRMATION.sql b/testing/data-generator/src/debug/assets/dummy_events/FACE_CAPTURE_CONFIRMATION.sql new file mode 100644 index 0000000000..d8da9b3263 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/FACE_CAPTURE_CONFIRMATION.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'FACE_CAPTURE_CONFIRMATION', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"endedAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":2,"result":"CONTINUE","type":"FACE_CAPTURE_CONFIRMATION"},"type":"FACE_CAPTURE_CONFIRMATION","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/FACE_FALLBACK_CAPTURE.sql b/testing/data-generator/src/debug/assets/dummy_events/FACE_FALLBACK_CAPTURE.sql new file mode 100644 index 0000000000..8a8cd80452 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/FACE_FALLBACK_CAPTURE.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'FACE_FALLBACK_CAPTURE', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"endedAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":2,"type":"FACE_FALLBACK_CAPTURE"},"type":"FACE_FALLBACK_CAPTURE","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/FINGERPRINT_CAPTURE.sql b/testing/data-generator/src/debug/assets/dummy_events/FINGERPRINT_CAPTURE.sql new file mode 100644 index 0000000000..8882c9638c --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/FINGERPRINT_CAPTURE.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'FINGERPRINT_CAPTURE', + '__project_id__', + 'b7b72945-0418-4ce4-99e4-c8c2efe435f9', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754216603588,"isTrustworthy":false},"eventVersion":4,"endedAt":{"ms":1754216603588,"isTrustworthy":false},"finger":"LEFT_THUMB","qualityThreshold":10,"result":"BAD_QUALITY","fingerprint":{"finger":"LEFT_THUMB","quality":8,"format":"ISO_19794_2"},"id":"__event_id__","type":"FINGERPRINT_CAPTURE"},"type":"FINGERPRINT_CAPTURE","scopeId":"b7b72945-0418-4ce4-99e4-c8c2efe435f9","projectId":"__project_id__"}', + 1754216603588, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/FINGERPRINT_CAPTURE_BIOMETRICS.sql b/testing/data-generator/src/debug/assets/dummy_events/FINGERPRINT_CAPTURE_BIOMETRICS.sql new file mode 100644 index 0000000000..4d529cfe80 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/FINGERPRINT_CAPTURE_BIOMETRICS.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'FINGERPRINT_CAPTURE_BIOMETRICS', + '__project_id__', + 'b7b72945-0418-4ce4-99e4-c8c2efe435f9', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754216603588,"isTrustworthy":false},"eventVersion":1,"fingerprint":{"finger":"LEFT_THUMB","template":"sometemplate","quality":10,"format":"ISO_19794_2"},"id":"__event_id__","type":"FINGERPRINT_CAPTURE_BIOMETRICS"},"type":"FINGERPRINT_CAPTURE_BIOMETRICS","scopeId":"b7b72945-0418-4ce4-99e4-c8c2efe435f9","projectId":"__project_id__"}', + 1754216603588, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/GUID_SELECTION.sql b/testing/data-generator/src/debug/assets/dummy_events/GUID_SELECTION.sql new file mode 100644 index 0000000000..da3ce29db0 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/GUID_SELECTION.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'GUID_SELECTION', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":2,"selectedId":"872271c4-695d-495d-910a-efde23097f8a","type":"GUID_SELECTION"},"type":"GUID_SELECTION","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/INTENT_PARSING.sql b/testing/data-generator/src/debug/assets/dummy_events/INTENT_PARSING.sql new file mode 100644 index 0000000000..1c9bca55dd --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/INTENT_PARSING.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'INTENT_PARSING', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":2,"integration":"COMMCARE","type":"INTENT_PARSING"},"type":"INTENT_PARSING","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/LICENSE_CHECK.sql b/testing/data-generator/src/debug/assets/dummy_events/LICENSE_CHECK.sql new file mode 100644 index 0000000000..1ac3f5546b --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/LICENSE_CHECK.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'LICENSE_CHECK', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":1,"status":"VALID","vendor":"NEC_FINGERPRINT","type":"LICENSE_CHECK"},"type":"LICENSE_CHECK","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/ONE_TO_MANY_MATCH.sql b/testing/data-generator/src/debug/assets/dummy_events/ONE_TO_MANY_MATCH.sql new file mode 100644 index 0000000000..e8485f1da3 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/ONE_TO_MANY_MATCH.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'ONE_TO_MANY_MATCH', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":3,"endedAt":{"ms":1754153597998,"isTrustworthy":false},"pool":{"type":"PROJECT","count":100},"matcher":"RANK_ONE","result":[{"candidateId":"872271c4-695d-495d-910a-efde23097f8a","score":0.0}],"probeBiometricReferenceId":"6ed2bfa5-d076-40ee-af33-ce7f4c826e23","type":"ONE_TO_MANY_MATCH"},"type":"ONE_TO_MANY_MATCH","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/ONE_TO_ONE_MATCH.sql b/testing/data-generator/src/debug/assets/dummy_events/ONE_TO_ONE_MATCH.sql new file mode 100644 index 0000000000..5b9c805b87 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/ONE_TO_ONE_MATCH.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'ONE_TO_ONE_MATCH', + '__project_id__', + '__session_id__', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754153597998,"isTrustworthy":false},"eventVersion":4,"endedAt":{"ms":1754153597998,"isTrustworthy":false},"candidateId":"872271c4-695d-495d-910a-efde23097f8a","matcher":"SIM_AFIS","result":{"candidateId":"872271c4-695d-495d-910a-efde23097f8a","score":10.0},"fingerComparisonStrategy":"CROSS_FINGER_USING_MEAN_OF_MAX","probeBiometricReferenceId":"6ed2bfa5-d076-40ee-af33-ce7f4c826e23","type":"ONE_TO_ONE_MATCH"},"type":"ONE_TO_ONE_MATCH","scopeId":"__scope_id__","projectId":"__project_id__"}', + 1754153597998, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/SCANNER_CONNECTION.sql b/testing/data-generator/src/debug/assets/dummy_events/SCANNER_CONNECTION.sql new file mode 100644 index 0000000000..564cc826d6 --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/SCANNER_CONNECTION.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'SCANNER_CONNECTION', + '__project_id__', + 'b7b72945-0418-4ce4-99e4-c8c2efe435f9', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754216603588,"isTrustworthy":false},"eventVersion":2,"scannerInfo":{"scannerId":"scanner_id","macAddress":"macaddress","generation":"VERO_1","hardwareVersion":"version"},"type":"SCANNER_CONNECTION"},"type":"SCANNER_CONNECTION","scopeId":"b7b72945-0418-4ce4-99e4-c8c2efe435f9","projectId":"__project_id__"}', + 1754216603588, + 0, + NULL + ); diff --git a/testing/data-generator/src/debug/assets/dummy_events/VERO_2_INFO_SNAPSHOT.sql b/testing/data-generator/src/debug/assets/dummy_events/VERO_2_INFO_SNAPSHOT.sql new file mode 100644 index 0000000000..94ba2bd8fc --- /dev/null +++ b/testing/data-generator/src/debug/assets/dummy_events/VERO_2_INFO_SNAPSHOT.sql @@ -0,0 +1,13 @@ +INSERT INTO + DbEvent +VALUES + ( + '__event_id__', + 'VERO_2_INFO_SNAPSHOT', + '__project_id__', + 'b7b72945-0418-4ce4-99e4-c8c2efe435f9', + '{"id":"__event_id__","payload":{"createdAt":{"ms":1754216603588,"isTrustworthy":false},"eventVersion":3,"battery":{"charge":0,"voltage":1,"current":2,"temperature":3},"version":{"hardwareRevision":"E-1","cypressApp":"cypressApp","stmApp":"stmApp","un20App":"un20App","master":0},"type":"VERO_2_INFO_SNAPSHOT"},"type":"VERO_2_INFO_SNAPSHOT","scopeId":"b7b72945-0418-4ce4-99e4-c8c2efe435f9","projectId":"__project_id__"}', + 1754216603588, + 0, + NULL + ); diff --git a/testing/data-generator/src/main/java/com/simprints/feature/datagenerator/DataGeneratorViewModel.kt b/testing/data-generator/src/main/java/com/simprints/feature/datagenerator/DataGeneratorViewModel.kt index b404c50564..3529dbe77d 100644 --- a/testing/data-generator/src/main/java/com/simprints/feature/datagenerator/DataGeneratorViewModel.kt +++ b/testing/data-generator/src/main/java/com/simprints/feature/datagenerator/DataGeneratorViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.simprints.feature.datagenerator.enrollmentrecords.InsertEnrollmentRecordsUseCase +import com.simprints.feature.datagenerator.events.InsertSessionEventsUseCase import com.simprints.infra.authstore.AuthStore import com.simprints.infra.logging.Simber import dagger.hilt.android.lifecycle.HiltViewModel @@ -14,6 +15,7 @@ import javax.inject.Inject @HiltViewModel internal class DataGeneratorViewModel @Inject constructor( private val insertEnrollmentRecords: InsertEnrollmentRecordsUseCase, + private val insertEvents: InsertSessionEventsUseCase, private val authStore: AuthStore, ) : ViewModel() { companion object { @@ -103,7 +105,7 @@ internal class DataGeneratorViewModel @Inject constructor( /** * Parses extras for generating session events and calls the data creation function. */ - private fun parseAndGenerateSessionEvents(intent: Intent) { + private suspend fun parseAndGenerateSessionEvents(intent: Intent) { val projectId = intent.getStringExtra(EXTRA_PROJECT_ID) val moduleId = intent.getStringExtra(EXTRA_MODULE_ID) val attendantId = intent.getStringExtra(EXTRA_ATTENDANT_ID) @@ -120,7 +122,18 @@ internal class DataGeneratorViewModel @Inject constructor( ) } - // Todo to be added later + insertEvents( + projectId = projectId, + moduleId = moduleId, + attendantId = attendantId, + enrolCount = enrolCount, + identifyCount = identifyCount, + confirmIdentifyCount = confirmIdentifyCount, + enrolLastCount = enrolLastCount, + verifyCount = verifyCount, + ).collect { + _statusMessage.postValue(it) + } } private fun extractBundleFromFlatExtras( diff --git a/testing/data-generator/src/main/java/com/simprints/feature/datagenerator/events/InsertSessionEventsUseCase.kt b/testing/data-generator/src/main/java/com/simprints/feature/datagenerator/events/InsertSessionEventsUseCase.kt new file mode 100644 index 0000000000..f9061710de --- /dev/null +++ b/testing/data-generator/src/main/java/com/simprints/feature/datagenerator/events/InsertSessionEventsUseCase.kt @@ -0,0 +1,168 @@ +package com.simprints.feature.datagenerator.events + +import com.simprints.core.DispatcherIO +import com.simprints.infra.events.EventRepository +import com.simprints.infra.events.event.domain.models.scope.EventScopeType +import com.simprints.infra.logging.Simber +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import javax.inject.Inject +import kotlin.random.Random + +class InsertSessionEventsUseCase @Inject constructor( + private val eventRepository: EventRepository, + private val sessionGenerator: SessionGenerator, + @DispatcherIO private val dispatcher: CoroutineDispatcher, +) { + operator fun invoke( + projectId: String, + moduleId: String, + attendantId: String, + enrolCount: Int, + identifyCount: Int, + verifyCount: Int, + confirmIdentifyCount: Int, + enrolLastCount: Int, + ) = flow { + val eventsCount = eventRepository.observeEventCount(null).first() + insertEnrollmentEvents(projectId, moduleId, attendantId, enrolCount) + emit("$enrolCount Enrollment sessions inserted successfully") + insertIdentifyEvents(projectId, moduleId, attendantId, identifyCount) + emit("$identifyCount Identify sessions inserted successfully") + insertVerifyEvents(projectId, moduleId, attendantId, verifyCount) + emit("$verifyCount Verify sessions inserted successfully") + insertConfirmIdentifyEvents(projectId, moduleId, attendantId, confirmIdentifyCount) + emit("$confirmIdentifyCount Confirm Identify sessions inserted successfully") + insertEnrolLastEvents(projectId, moduleId, attendantId, enrolLastCount) + emit("$enrolLastCount Enrol Last sessions inserted successfully") + + val newEventsCount = eventRepository.observeEventCount(null).first() + Simber.i( + "Generated ${newEventsCount - eventsCount} events", + tag = "InsertSessionEventsUseCase", + ) + + emit("Generated a total of ${newEventsCount - eventsCount} new events") + sessionGenerator.clearCache() + }.flowOn(dispatcher) + + private suspend fun insertEnrollmentEvents( + projectId: String, + moduleId: String, + attendantId: String, + enrolCount: Int, + ) { + batchInsertEvents(count = enrolCount) { scopeId -> + when (Random.nextInt(2)) { + 0 -> sessionGenerator.generateEnrolmentIso( + projectId = projectId, + moduleId = moduleId, + attendantId = attendantId, + scopeId = scopeId, + ) + + else -> sessionGenerator.generateEnrolmentSimFace( + projectId = projectId, + moduleId = moduleId, + attendantId = attendantId, + scopeId = scopeId, + ) + } + } + } + + private suspend fun insertIdentifyEvents( + projectId: String, + moduleId: String, + attendantId: String, + identifyCount: Int, + ) { + batchInsertEvents(count = identifyCount) { scopeId -> + sessionGenerator.generateIdentificationRoc3( + projectId = projectId, + moduleId = moduleId, + attendantId = attendantId, + scopeId = scopeId, + ) + } + } + + private suspend fun insertVerifyEvents( + projectId: String, + moduleId: String, + attendantId: String, + verifyCount: Int, + ) { + batchInsertEvents(count = verifyCount) { scopeId -> + sessionGenerator.generateVerificationRoc3( + projectId = projectId, + moduleId = moduleId, + attendantId = attendantId, + scopeId = scopeId, + ) + } + } + + private suspend fun insertConfirmIdentifyEvents( + projectId: String, + moduleId: String, + attendantId: String, + confirmIdentifyCount: Int, + ) { + batchInsertEvents(count = confirmIdentifyCount) { scopeId -> + sessionGenerator.generateConfirmationRoc3( + projectId = projectId, + moduleId = moduleId, + attendantId = attendantId, + scopeId = scopeId, + ) + } + } + + private suspend fun insertEnrolLastEvents( + projectId: String, + moduleId: String, + attendantId: String, + enrolLastCount: Int, + ) { + batchInsertEvents(count = enrolLastCount) { scopeId -> + sessionGenerator.generateEnrolLastBioRoc3( + projectId = projectId, + moduleId = moduleId, + attendantId = attendantId, + scopeId = scopeId, + ) + } + } + + private suspend fun batchInsertEvents( + count: Int, + generateCommands: (scopeId: String) -> List, + ) { + val eventsToInsert = mutableListOf() + repeat(count) { + val scopeId = createEventScope() + val dbInsertionCommands = generateCommands(scopeId) + eventsToInsert.addAll(dbInsertionCommands) + if (eventsToInsert.size >= BATCH_SIZE) { + eventRepository.executeRawEventInsertions(eventsToInsert) + eventsToInsert.clear() + } + } + if (eventsToInsert.isNotEmpty()) { + eventRepository.executeRawEventInsertions(eventsToInsert) + } + } + + private suspend fun createEventScope(): String { + val eventScope = eventRepository.createEventScope(EventScopeType.SESSION) + eventRepository.closeEventScope(eventScope, null) + return eventScope.id + } + + companion object { + const val BATCH_SIZE = 200 + } +} diff --git a/testing/data-generator/src/main/java/com/simprints/feature/datagenerator/events/SessionGenerator.kt b/testing/data-generator/src/main/java/com/simprints/feature/datagenerator/events/SessionGenerator.kt new file mode 100644 index 0000000000..87489887c9 --- /dev/null +++ b/testing/data-generator/src/main/java/com/simprints/feature/datagenerator/events/SessionGenerator.kt @@ -0,0 +1,244 @@ +package com.simprints.feature.datagenerator.events + +import javax.inject.Inject + +class SessionGenerator @Inject constructor( + private val sqlEventTemplateLoader: SqlEventTemplateLoader, +) { + /** + * Corresponds to `Enrollment-roc-3.json`. + * A session where a user is identified and then a new enrolment is created. + */ + fun generateEnrolLastBioRoc3( + projectId: String, + attendantId: String, + moduleId: String, + scopeId: String, + ) = loadEventsSql( + eventNames = ENROL_LAST_BIO_ROC3_EVENTS, + projectId = projectId, + attendantId = attendantId, + moduleId = moduleId, + scopeId = scopeId, + ) + + /** + * Corresponds to `Identification-roc-3.json`. + * A standard identification session. + */ + fun generateIdentificationRoc3( + projectId: String, + attendantId: String, + moduleId: String, + scopeId: String, + ) = loadEventsSql( + eventNames = IDENTIFICATION_ROC3_EVENTS, + projectId = projectId, + attendantId = attendantId, + moduleId = moduleId, + scopeId = scopeId, + ) + + /** + * Corresponds to `Verification-roc-3.json`. + * A standard verification session against a known GUID. + */ + fun generateVerificationRoc3( + projectId: String, + attendantId: String, + moduleId: String, + scopeId: String, + ) = loadEventsSql( + eventNames = VERIFICATION_ROC3_EVENTS, + projectId = projectId, + attendantId = attendantId, + moduleId = moduleId, + scopeId = scopeId, + ) + + /** + * Corresponds to `Confirmation-roc-3.json`. + * An identification followed by a user selecting a GUID for confirmation. + */ + fun generateConfirmationRoc3( + projectId: String, + attendantId: String, + moduleId: String, + scopeId: String, + ) = loadEventsSql( + eventNames = CONFIRMATION_ROC3_EVENTS, + projectId = projectId, + attendantId = attendantId, + moduleId = moduleId, + scopeId = scopeId, + ) + + /** + * Corresponds to `Enrolment-SimFace.json`. + * A direct face enrolment using Simprints' face algorithm. + */ + fun generateEnrolmentSimFace( + projectId: String, + attendantId: String, + moduleId: String, + scopeId: String, + ) = loadEventsSql( + eventNames = ENROLMENT_SIMFACE_EVENTS, + projectId = projectId, + attendantId = attendantId, + moduleId = moduleId, + scopeId = scopeId, + ) + + /** + * Corresponds to `Enrolment-ISO.json`. + * A fingerprint enrolment session using a vero scanner ISO templates. + */ + fun generateEnrolmentIso( + projectId: String, + attendantId: String, + moduleId: String, + scopeId: String, + ) = loadEventsSql( + eventNames = ENROLMENT_ISO_EVENTS, + projectId = projectId, + attendantId = attendantId, + moduleId = moduleId, + scopeId = scopeId, + ) + + private fun loadEventsSql( + eventNames: List, + projectId: String, + attendantId: String, + moduleId: String, + scopeId: String, + ) = eventNames.map { eventName -> + sqlEventTemplateLoader.getSql( + eventName = eventName, + projectId = projectId, + attendantId = attendantId, + moduleId = moduleId, + scopeId = scopeId, + ) + } + + fun clearCache() { + sqlEventTemplateLoader.clearCache() + } + + companion object { + val ENROL_LAST_BIO_ROC3_EVENTS = listOf( + "INTENT_PARSING", + "CONNECTIVITY_SNAPSHOT", + "CALLOUT_IDENTIFICATION", + "AUTHORIZATION", + "CONSENT", + "FACE_FALLBACK_CAPTURE", + "LICENSE_CHECK", + "FACE_CAPTURE", + "FACE_CAPTURE_BIOMETRICS", + "FACE_CAPTURE", + "FACE_CAPTURE_BIOMETRICS", + "FACE_CAPTURE_CONFIRMATION", + "BIOMETRIC_REFERENCE_CREATION", + "ONE_TO_MANY_MATCH", + "CALLBACK_IDENTIFICATION", + "CALLOUT_ENROLMENT", + "AUTHORIZATION", + "ENROLMENT", + "CALLBACK_ENROLMENT", + "COMPLETION_CHECK", + ) + + val IDENTIFICATION_ROC3_EVENTS = listOf( + "INTENT_PARSING", + "CONNECTIVITY_SNAPSHOT", + "CALLOUT_IDENTIFICATION", + "AUTHORIZATION", + "CONSENT", + "FACE_FALLBACK_CAPTURE", + "LICENSE_CHECK", + "FACE_CAPTURE", + "FACE_CAPTURE_BIOMETRICS", + "FACE_CAPTURE_CONFIRMATION", + "BIOMETRIC_REFERENCE_CREATION", + "ONE_TO_MANY_MATCH", + "CALLBACK_IDENTIFICATION", + "COMPLETION_CHECK", + ) + + val VERIFICATION_ROC3_EVENTS = listOf( + "INTENT_PARSING", + "CONNECTIVITY_SNAPSHOT", + "CALLOUT_VERIFICATION", + "AUTHORIZATION", + "CANDIDATE_READ", + "CONSENT", + "FACE_FALLBACK_CAPTURE", + "LICENSE_CHECK", + "FACE_CAPTURE", + "FACE_CAPTURE_BIOMETRICS", + "FACE_CAPTURE_CONFIRMATION", + "BIOMETRIC_REFERENCE_CREATION", + "ONE_TO_ONE_MATCH", + "CALLBACK_VERIFICATION", + "COMPLETION_CHECK", + ) + + val CONFIRMATION_ROC3_EVENTS = listOf( + "INTENT_PARSING", + "CONNECTIVITY_SNAPSHOT", + "CALLOUT_IDENTIFICATION", + "AUTHORIZATION", + "CONSENT", + "FACE_FALLBACK_CAPTURE", + "LICENSE_CHECK", + "FACE_CAPTURE", + "FACE_CAPTURE_CONFIRMATION", + "BIOMETRIC_REFERENCE_CREATION", + "ONE_TO_MANY_MATCH", + "CALLBACK_IDENTIFICATION", + "COMPLETION_CHECK", + "CALLOUT_CONFIRMATION", + "AUTHORIZATION", + "GUID_SELECTION", + "CALLBACK_CONFIRMATION", + "COMPLETION_CHECK", + ) + + val ENROLMENT_SIMFACE_EVENTS = listOf( + "INTENT_PARSING", + "CONNECTIVITY_SNAPSHOT", + "CALLOUT_ENROLMENT", + "AUTHORIZATION", + "CONSENT", + "FACE_FALLBACK_CAPTURE", + "FACE_CAPTURE", + "FACE_CAPTURE_BIOMETRICS", + "FACE_CAPTURE_CONFIRMATION", + "BIOMETRIC_REFERENCE_CREATION", + "ENROLMENT", + "CALLBACK_ENROLMENT", + "COMPLETION_CHECK", + ) + + val ENROLMENT_ISO_EVENTS = listOf( + "INTENT_PARSING", + "CONNECTIVITY_SNAPSHOT", + "CALLOUT_ENROLMENT", + "AUTHORIZATION", + "CONSENT", + "SCANNER_CONNECTION", + "VERO_2_INFO_SNAPSHOT", + "FINGERPRINT_CAPTURE", + "FINGERPRINT_CAPTURE_BIOMETRICS", + "FINGERPRINT_CAPTURE", + "FINGERPRINT_CAPTURE_BIOMETRICS", + "BIOMETRIC_REFERENCE_CREATION", + "ENROLMENT", + "CALLBACK_ENROLMENT", + "COMPLETION_CHECK", + ) + } +} diff --git a/testing/data-generator/src/main/java/com/simprints/feature/datagenerator/events/SqlEventTemplateLoader.kt b/testing/data-generator/src/main/java/com/simprints/feature/datagenerator/events/SqlEventTemplateLoader.kt new file mode 100644 index 0000000000..9f474188f3 --- /dev/null +++ b/testing/data-generator/src/main/java/com/simprints/feature/datagenerator/events/SqlEventTemplateLoader.kt @@ -0,0 +1,66 @@ +package com.simprints.feature.datagenerator.events + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap +import javax.inject.Inject + +/** + * Loads SQL event files from assets/dummy_events and replaces placeholders + * with provided values. Cached for performance. + */ +class SqlEventTemplateLoader @Inject constructor( + @ApplicationContext private val context: Context, +) { + private val cache = ConcurrentHashMap() + + fun getSql( + eventName: String, + projectId: String, + attendantId: String, + moduleId: String, + scopeId: String, + ): String { + val template = cache.getOrPut(eventName) { + readTemplateFromAssets(context, eventName) + } + + return template + .replace(PROJECT_ID_PLACEHOLDER, projectId) + .replace(ATTENDANT_ID_PLACEHOLDER, attendantId) + .replace(MODULE_ID_PLACEHOLDER, moduleId) + .replace(SCOPE_ID_PLACEHOLDER, scopeId) + .replace(SESSION_ID_PLACEHOLDER, scopeId) + .replace(EVENT_ID_PLACEHOLDER, UUID.randomUUID().toString()) + } + + /** Reads the file from assets/dummy_events/.sql */ + private fun readTemplateFromAssets( + context: Context, + eventName: String, + ): String { + val assetManager = context.assets + val filePath = "$BASE_PATH/$eventName.sql" + + return try { + assetManager.open(filePath).bufferedReader().use { it.readText() } + } catch (e: Exception) { + throw IllegalArgumentException("Failed to load SQL file for event '$eventName': $filePath", e) + } + } + + fun clearCache() { + cache.clear() + } + + companion object { + private const val BASE_PATH = "dummy_events" + private const val PROJECT_ID_PLACEHOLDER = "__project_id__" + private const val ATTENDANT_ID_PLACEHOLDER = "__attendant_id__" + private const val MODULE_ID_PLACEHOLDER = "__module_id__" + private const val SCOPE_ID_PLACEHOLDER = "__scope_id__" + private const val SESSION_ID_PLACEHOLDER = "__session_id__" + private const val EVENT_ID_PLACEHOLDER = "__event_id__" + } +} diff --git a/testing/data-generator/src/test/java/com/simprints/feature/datagenerator/DataGeneratorViewModelTest.kt b/testing/data-generator/src/test/java/com/simprints/feature/datagenerator/DataGeneratorViewModelTest.kt index 62173fa5d8..2fc4ac036a 100644 --- a/testing/data-generator/src/test/java/com/simprints/feature/datagenerator/DataGeneratorViewModelTest.kt +++ b/testing/data-generator/src/test/java/com/simprints/feature/datagenerator/DataGeneratorViewModelTest.kt @@ -5,6 +5,7 @@ import androidx.test.ext.junit.runners.* import com.google.common.truth.Truth.* import com.simprints.feature.datagenerator.DataGeneratorViewModel import com.simprints.feature.datagenerator.enrollmentrecords.InsertEnrollmentRecordsUseCase +import com.simprints.feature.datagenerator.events.InsertSessionEventsUseCase import com.simprints.infra.authstore.AuthStore import io.mockk.* import io.mockk.impl.annotations.MockK @@ -27,6 +28,9 @@ internal class DataGeneratorViewModelTest { @MockK private lateinit var insertEnrollmentRecordsUseCase: InsertEnrollmentRecordsUseCase + @MockK + private lateinit var insertSessionEventsUseCase: InsertSessionEventsUseCase + @MockK private lateinit var authStore: AuthStore @@ -47,7 +51,11 @@ internal class DataGeneratorViewModelTest { fun setUp() { MockKAnnotations.init(this) every { authStore.signedInProjectId } returns "test_project_id" - viewModel = DataGeneratorViewModel(insertEnrollmentRecordsUseCase, authStore) + viewModel = DataGeneratorViewModel( + insertEnrollmentRecordsUseCase, + insertSessionEventsUseCase, + authStore, + ) } @Test(expected = IllegalArgumentException::class) @@ -204,4 +212,60 @@ internal class DataGeneratorViewModelTest { val capturedFingerOrder = fingerOrderSlot.captured assertThat(capturedFingerOrder.getString("ISO_19794_2")).isEqualTo("LEFT_THUMB,LEFT_INDEX_FINGER") } + + @Test + fun `handleIntent with GENERATE_SESSION_EVENTS and valid extras calls use case and updates status`() = runTest { + // Given + val projectId = "proj1" + val moduleId = "mod1" + val attendantId = "att1" + val enrolCount = 10 + val identifyCount = 5 + val verifyCount = 3 + val confirmIdentifyCount = 2 + val enrolLastCount = 1 + + val intent = Intent("com.simprints.test.GENERATE_SESSION_EVENTS").apply { + putExtra(EXTRA_PROJECT_ID, projectId) + putExtra(EXTRA_MODULE_ID, moduleId) + putExtra(EXTRA_ATTENDANT_ID, attendantId) + putExtra("EXTRA_ENROL_COUNT", enrolCount) + putExtra("EXTRA_IDENTIFY_COUNT", identifyCount) + putExtra("EXTRA_VERIFY_COUNT", verifyCount) + putExtra("EXTRA_CONFIRM_IDENTIFY_COUNT", confirmIdentifyCount) + putExtra("EXTRA_ENROL_LAST_COUNT", enrolLastCount) + } + + coEvery { + insertSessionEventsUseCase.invoke( + projectId, + moduleId, + attendantId, + enrolCount, + identifyCount, + verifyCount, + confirmIdentifyCount, + enrolLastCount, + ) + } returns flowOf("Inserted session events successfully") + + // When + viewModel.handleIntent(intent) + + // Then + coVerify(exactly = 1) { + insertSessionEventsUseCase.invoke( + projectId, + moduleId, + attendantId, + enrolCount, + identifyCount, + verifyCount, + confirmIdentifyCount, + enrolLastCount, + ) + } + + assertThat(viewModel.statusMessage.value).isEqualTo("Inserted session events successfully") + } } diff --git a/testing/data-generator/src/test/java/com/simprints/feature/datagenerator/events/InsertSessionEventsUseCaseTest.kt b/testing/data-generator/src/test/java/com/simprints/feature/datagenerator/events/InsertSessionEventsUseCaseTest.kt new file mode 100644 index 0000000000..916ee5576c --- /dev/null +++ b/testing/data-generator/src/test/java/com/simprints/feature/datagenerator/events/InsertSessionEventsUseCaseTest.kt @@ -0,0 +1,117 @@ +package com.simprints.feature.datagenerator.events + +import com.google.common.truth.Truth.* +import com.simprints.infra.events.EventRepository +import com.simprints.infra.events.event.domain.models.scope.EventScope +import com.simprints.infra.events.event.domain.models.scope.EventScopeType +import io.mockk.* +import io.mockk.impl.annotations.* +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Before +import org.junit.Test +import kotlin.random.Random + +class InsertSessionEventsUseCaseTest { + private val testDispatcher = StandardTestDispatcher() + + @RelaxedMockK + private lateinit var mockEventRepository: EventRepository + + @MockK + private lateinit var mockSessionGenerator: SessionGenerator + + private lateinit var useCase: InsertSessionEventsUseCase + + @Before + fun setUp() { + MockKAnnotations.init(this) + mockkObject(Random.Default) + useCase = InsertSessionEventsUseCase( + eventRepository = mockEventRepository, + sessionGenerator = mockSessionGenerator, + dispatcher = testDispatcher, + ) + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `invoke SHOULD insert all event types and emit correct progress messages`() = runTest(testDispatcher) { + // GIVEN + val projectId = "project-1" + val moduleId = "module-1" + val attendantId = "attendant-1" + val enrolCount = 2 + val identifyCount = 1 + val verifyCount = 1 + val confirmIdentifyCount = 1 + val enrolLastCount = 1 + val totalNewSessions = enrolCount + identifyCount + verifyCount + confirmIdentifyCount + enrolLastCount + + val initialEventCount = 10 + val finalEventCount = 50 // Assuming we expect 40 new events to be generated + val mockEventScope = mockk(relaxed = true) + val mockDbCommands = listOf("INSERT INTO ...") + + // Mock initial and final event counts from the repository + coEvery { mockEventRepository.observeEventCount(null) } returns flowOf(initialEventCount) andThen flowOf(finalEventCount) + + // Mock event scope creation and closing (suspend functions) + coEvery { mockEventRepository.createEventScope(EventScopeType.SESSION) } returns mockEventScope + coJustRun { mockEventRepository.closeEventScope(any(), null) } + + // Mock event insertion (suspend function) + coEvery { mockEventRepository.executeRawEventInsertions(any()) } returns Unit + + // Mock the Random generator to always choose the first path (ISO) in insertEnrollmentEvents + every { Random.nextInt(2) } returns 0 + + // Mock all session generator calls + every { mockSessionGenerator.generateEnrolmentIso(any(), any(), any(), any()) } returns mockDbCommands + every { mockSessionGenerator.generateIdentificationRoc3(any(), any(), any(), any()) } returns mockDbCommands + every { mockSessionGenerator.generateVerificationRoc3(any(), any(), any(), any()) } returns mockDbCommands + every { mockSessionGenerator.generateConfirmationRoc3(any(), any(), any(), any()) } returns mockDbCommands + every { mockSessionGenerator.generateEnrolLastBioRoc3(any(), any(), any(), any()) } returns mockDbCommands + every { mockSessionGenerator.clearCache() } returns Unit + + // WHEN: Execute the use case and collect all emitted values from the flow + val emissions = useCase( + projectId = projectId, + moduleId = moduleId, + attendantId = attendantId, + enrolCount = enrolCount, + identifyCount = identifyCount, + verifyCount = verifyCount, + confirmIdentifyCount = confirmIdentifyCount, + enrolLastCount = enrolLastCount, + ).toList() + + // THEN + + assertThat(emissions) + .containsExactly( + "$enrolCount Enrollment sessions inserted successfully", + "$identifyCount Identify sessions inserted successfully", + "$verifyCount Verify sessions inserted successfully", + "$confirmIdentifyCount Confirm Identify sessions inserted successfully", + "$enrolLastCount Enrol Last sessions inserted successfully", + "Generated a total of ${finalEventCount - initialEventCount} new events", + ).inOrder() + + coVerify(exactly = enrolCount) { mockSessionGenerator.generateEnrolmentIso(any(), any(), any(), any()) } + coVerify(exactly = identifyCount) { mockSessionGenerator.generateIdentificationRoc3(any(), any(), any(), any()) } + coVerify(exactly = verifyCount) { mockSessionGenerator.generateVerificationRoc3(any(), any(), any(), any()) } + coVerify(exactly = confirmIdentifyCount) { mockSessionGenerator.generateConfirmationRoc3(any(), any(), any(), any()) } + coVerify(exactly = enrolLastCount) { mockSessionGenerator.generateEnrolLastBioRoc3(any(), any(), any(), any()) } + coVerify(exactly = totalNewSessions) { mockEventRepository.createEventScope(EventScopeType.SESSION) } + coVerify(exactly = 1) { mockSessionGenerator.clearCache() } + } +} diff --git a/testing/data-generator/src/test/java/com/simprints/feature/datagenerator/events/SessionGeneratorTest.kt b/testing/data-generator/src/test/java/com/simprints/feature/datagenerator/events/SessionGeneratorTest.kt new file mode 100644 index 0000000000..089f771888 --- /dev/null +++ b/testing/data-generator/src/test/java/com/simprints/feature/datagenerator/events/SessionGeneratorTest.kt @@ -0,0 +1,118 @@ +package com.simprints.feature.datagenerator.events + +import com.google.common.truth.Truth.* +import io.mockk.* +import io.mockk.impl.annotations.MockK +import org.junit.Before +import org.junit.Test + +class SessionGeneratorTest { + @MockK + private lateinit var mockSqlLoader: SqlEventTemplateLoader + + private lateinit var sessionGenerator: SessionGenerator + + @Before + fun setUp() { + MockKAnnotations.init(this) + sessionGenerator = SessionGenerator(mockSqlLoader) + } + + @Test + fun `generateIdentificationRoc3 SHOULD request sql for each event in its list`() { + // GIVEN + val projectId = "project-1" + val attendantId = "attendant-1" + val moduleId = "module-1" + val scopeId = "scope-1" + + val expectedEventNames = SessionGenerator.IDENTIFICATION_ROC3_EVENTS + + expectedEventNames.forEach { eventName -> + every { + mockSqlLoader.getSql( + eventName = eventName, + projectId = projectId, + attendantId = attendantId, + moduleId = moduleId, + scopeId = scopeId, + ) + } returns "SQL for $eventName" + } + + // WHEN + val resultSqlList = sessionGenerator.generateIdentificationRoc3( + projectId = projectId, + attendantId = attendantId, + moduleId = moduleId, + scopeId = scopeId, + ) + + // THEN + + expectedEventNames.forEach { eventName -> + verify(exactly = 1) { + mockSqlLoader.getSql( + eventName = eventName, + projectId = projectId, + attendantId = attendantId, + moduleId = moduleId, + scopeId = scopeId, + ) + } + } + + assertThat(resultSqlList).hasSize(expectedEventNames.size) + + assertThat(resultSqlList.first()).isEqualTo("SQL for ${expectedEventNames.first()}") + assertThat(resultSqlList.last()).isEqualTo("SQL for ${expectedEventNames.last()}") + } + + @Test + fun `generateEnrolmentIso SHOULD request sql for each event in its list`() { + // GIVEN + val projectId = "project-1" + val attendantId = "attendant-1" + val moduleId = "module-1" + val scopeId = "scope-1" + + val expectedEventNames = SessionGenerator.ENROLMENT_ISO_EVENTS + + every { mockSqlLoader.getSql(any(), any(), any(), any(), any()) } returns "some generated SQL" + + // WHEN + val resultSqlList = sessionGenerator.generateEnrolmentIso( + projectId = projectId, + attendantId = attendantId, + moduleId = moduleId, + scopeId = scopeId, + ) + + // THEN + expectedEventNames.forEach { eventName -> + verify { + mockSqlLoader.getSql( + eventName = eventName, + projectId = projectId, + attendantId = attendantId, + moduleId = moduleId, + scopeId = scopeId, + ) + } + } + + assertThat(resultSqlList).hasSize(expectedEventNames.size) + } + + @Test + fun `clearCache SHOULD delegate the call to the sql event loader`() { + // GIVEN + every { mockSqlLoader.clearCache() } returns Unit + + // WHEN + sessionGenerator.clearCache() + + // THEN + verify(exactly = 1) { mockSqlLoader.clearCache() } + } +} diff --git a/testing/data-generator/src/test/java/com/simprints/feature/datagenerator/events/SqlEventTemplateLoaderTest.kt b/testing/data-generator/src/test/java/com/simprints/feature/datagenerator/events/SqlEventTemplateLoaderTest.kt new file mode 100644 index 0000000000..57eb0615b1 --- /dev/null +++ b/testing/data-generator/src/test/java/com/simprints/feature/datagenerator/events/SqlEventTemplateLoaderTest.kt @@ -0,0 +1,140 @@ +package com.simprints.feature.datagenerator.events + +import android.content.Context +import android.content.res.AssetManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.unmockkAll +import io.mockk.verify +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.io.IOException +import kotlin.test.assertFailsWith + +@RunWith(AndroidJUnit4::class) +class SqlEventTemplateLoaderTest { + @MockK + private lateinit var mockContext: Context + + @MockK + private lateinit var mockAssetManager: AssetManager + + private lateinit var loader: SqlEventTemplateLoader + + @Before + fun setUp() { + MockKAnnotations.init(this) + every { mockContext.assets } returns mockAssetManager + loader = SqlEventTemplateLoader(mockContext) + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `getSql WHEN cache is empty SHOULD load from assets and replace placeholders`() { + // Given + val eventName = "test_event" + val projectId = "proj-123" + val attendantId = "attend-456" + val moduleId = "mod-789" + val scopeId = "scope-abc" + val filePath = "dummy_events/$eventName.sql" + + val template = + """ + INSERT INTO ... + VALUES ('__project_id__', '__attendant_id__', '__module_id__', '__scope_id__', '__session_id__'); + """.trimIndent() + + // This is the final SQL we expect after placeholder replacement + val expectedSql = + """ + INSERT INTO ... + VALUES ('$projectId', '$attendantId', '$moduleId', '$scopeId', '$scopeId'); + """.trimIndent() + + every { mockAssetManager.open(filePath) } returns template.byteInputStream() + + // When + val result = loader.getSql( + eventName = eventName, + projectId = projectId, + attendantId = attendantId, + moduleId = moduleId, + scopeId = scopeId, + ) + + // Then + assertThat(result).isEqualTo(expectedSql) + + verify { mockAssetManager.open(filePath) } + } + + @Test + fun `getSql WHEN cache is populated SHOULD use cache and not load from assets`() { + // Given + val eventName = "cached_event" + val filePath = "dummy_events/$eventName.sql" + val template = "insert into ... project_id = '__project_id__';" + + every { mockAssetManager.open(filePath) } returns template.byteInputStream() + + // When + loader.getSql(eventName, "proj-1", "att-1", "mod-1", "scope-1") + + loader.getSql(eventName, "proj-2", "att-2", "mod-2", "scope-2") + + // Then + // Verify that open() was called only once, proving the cache was used for the second call + verify { mockAssetManager.open(filePath) } + } + + @Test + fun `getSql WHEN asset file does not exist SHOULD throw IllegalArgumentException`() { + // Given + val eventName = "non_existent_event" + val filePath = "dummy_events/$eventName.sql" + + every { mockAssetManager.open(filePath) } throws IOException("File not found!") + + // When & Then + val exception = assertFailsWith { + loader.getSql(eventName, "p", "a", "m", "s") + } + + assertThat(exception) + .hasMessageThat() + .isEqualTo("Failed to load SQL file for event '$eventName': $filePath") + assertThat(exception.cause).isInstanceOf(IOException::class.java) + assertThat(exception.cause).hasMessageThat().isEqualTo("File not found!") + } + + @Test + fun `clearCache SHOULD empty the cache and force reload from assets`() { + // Given + val eventName = "event_to_clear" + val filePath = "dummy_events/$eventName.sql" + val template = "insert into ... '__module_id__';" + + every { mockAssetManager.open(filePath) } returns template.byteInputStream() + + loader.getSql(eventName, "p", "a", "m", "s") + verify { mockAssetManager.open(filePath) } + + // When + loader.clearCache() + + loader.getSql(eventName, "p", "a", "m", "s") + + // Then + verify { mockAssetManager.open(filePath) } + } +}