From 416a5b0506ddd626fee09182e281ccf8feaac356 Mon Sep 17 00:00:00 2001 From: paul sandjong Date: Thu, 11 Dec 2025 09:49:51 +0100 Subject: [PATCH 01/10] test claimAct.saveClaim --- claimManagement/build.gradle | 19 ++- .../openimis/imisclaims/ClaimActivity.java | 2 +- .../imisclaims/ClaimActivityTest.java | 139 ++++++++++++++++++ .../ClaimManagementActivityTest.java | 7 - 4 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java delete mode 100644 claimManagement/src/test/java/org/openimis/imisclaims/ClaimManagementActivityTest.java diff --git a/claimManagement/build.gradle b/claimManagement/build.gradle index c3a245d7..8cd81622 100644 --- a/claimManagement/build.gradle +++ b/claimManagement/build.gradle @@ -180,6 +180,12 @@ android { packagingOptions { exclude 'META-INF/DEPENDENCIES' } + + testOptions { + unitTests { + includeAndroidResources = true + } + } } apollo { @@ -193,6 +199,13 @@ apollo { ] } +tasks.withType(Test).configureEach { + testLogging { + events "passed", "skipped", "failed" + exceptionFormat "full" + showStandardStreams = false + } +} // Apply custom flavours if(file('custom-flavours.gradle').exists()){ @@ -215,9 +228,13 @@ dependencies { implementation ('com.apollographql.apollo:apollo-android-support:2.5.14'){ because("Apollo 3+ only works with Kotlin coroutines") } - testImplementation 'junit:junit:4.12' + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.mockito:mockito-core:5.5.0' + testImplementation 'org.robolectric:robolectric:4.11.1' + testImplementation 'androidx.test:core:1.5.0' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'androidx.test:core:1.5.0' implementation group: 'com.squareup.picasso', name: 'picasso', version: '2.71828' implementation group: 'net.lingala.zip4j', name: 'zip4j', version: '1.2.7' diff --git a/claimManagement/src/main/java/org/openimis/imisclaims/ClaimActivity.java b/claimManagement/src/main/java/org/openimis/imisclaims/ClaimActivity.java index e9894971..70990e8e 100644 --- a/claimManagement/src/main/java/org/openimis/imisclaims/ClaimActivity.java +++ b/claimManagement/src/main/java/org/openimis/imisclaims/ClaimActivity.java @@ -811,7 +811,7 @@ protected void confirmNewDialog(String msg) { runOnUiThread(() -> showDialog(msg, (dialog, which) -> ClearForm(), (dialog, which) -> dialog.dismiss())); } - private boolean saveClaim() { + protected boolean saveClaim() { Intent intent = getIntent(); String claimUUID; if (intent.hasExtra(EXTRA_CLAIM_UUID)) { diff --git a/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java b/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java new file mode 100644 index 00000000..1255f9ba --- /dev/null +++ b/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java @@ -0,0 +1,139 @@ +package org.openimis.imisclaims; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import android.app.Activity; +import android.content.ContentValues; +import android.content.res.Resources; +import android.widget.AutoCompleteTextView; +import android.widget.CheckBox; +import android.widget.EditText; +import android.text.SpannableStringBuilder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.openimis.imisclaims.tools.StorageManager; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; + +import java.util.ArrayList; +import java.util.HashMap; + +@RunWith(RobolectricTestRunner.class) +public class ClaimActivityTest { + @Mock + Global global; + + @Mock + SQLHandler sqlHandler; + + @Mock + Activity activity; + + @Mock + Resources resources; + + @Mock + StorageManager storageManager; + + ClaimActivity claimActivity; + + @Before + public void setup() { + MockitoAnnotations.openMocks(this); + when(activity.getResources()).thenReturn(resources); + when(resources.getString(anyInt())).thenReturn("mockString"); + claimActivity = Robolectric.buildActivity(ClaimActivity.class).create().start().resume().visible().get(); + claimActivity.sqlHandler = sqlHandler; + claimActivity.global = global; + when(global.isNetworkAvailable()).thenReturn(true); // or false, it doesn't matter + } + + @Test + public void testSaveClaim_ShouldInsertInDatabase() { + EditText etHF = mock(EditText.class); + EditText etClaimAdmin = mock(EditText.class); + EditText etClaimCode = mock(EditText.class); + EditText etGuaranteeNo = mock(EditText.class); + EditText etCHFID = mock(EditText.class); + EditText etStart = mock(EditText.class); + EditText etEnd = mock(EditText.class); + + AutoCompleteTextView etDiagnosis = mock(AutoCompleteTextView.class); + AutoCompleteTextView etDiagnosis1 = mock(AutoCompleteTextView.class); + AutoCompleteTextView etDiagnosis2 = mock(AutoCompleteTextView.class); + AutoCompleteTextView etDiagnosis3 = mock(AutoCompleteTextView.class); + AutoCompleteTextView etDiagnosis4 = mock(AutoCompleteTextView.class); + + when(etHF.getText()).thenReturn(new SpannableStringBuilder("HF001")); + when(etClaimAdmin.getText()).thenReturn(new SpannableStringBuilder("ADMIN001")); + when(etClaimCode.getText()).thenReturn(new SpannableStringBuilder("CLM001")); + when(etGuaranteeNo.getText()).thenReturn(new SpannableStringBuilder("GNT001")); + when(etCHFID.getText()).thenReturn(new SpannableStringBuilder("INS12345")); + when(etStart.getText()).thenReturn(new SpannableStringBuilder("2025-12-01")); + when(etEnd.getText()).thenReturn(new SpannableStringBuilder("2025-12-09")); + when(etDiagnosis.getText()).thenReturn(new SpannableStringBuilder("A01")); + when(etDiagnosis1.getText()).thenReturn(new SpannableStringBuilder("B01")); + when(etDiagnosis2.getText()).thenReturn(new SpannableStringBuilder("C01")); + when(etDiagnosis3.getText()).thenReturn(new SpannableStringBuilder("D01")); + when(etDiagnosis4.getText()).thenReturn(new SpannableStringBuilder("E01")); + + AutoCompleteTextView etVisitType = mock(AutoCompleteTextView.class); + AutoCompleteTextView patientCondition = mock(AutoCompleteTextView.class); + when(etVisitType.getTag()).thenReturn("O"); + when(patientCondition.getTag()).thenReturn("A"); + + CheckBox etPreAuth = mock(CheckBox.class); + when(etPreAuth.isChecked()).thenReturn(false); + + claimActivity.etHealthFacility = etHF; + claimActivity.etClaimAdmin = etClaimAdmin; + claimActivity.etClaimCode = etClaimCode; + claimActivity.etGuaranteeNo = etGuaranteeNo; + claimActivity.etInsureeNumber = etCHFID; + claimActivity.etStartDate = etStart; + claimActivity.etEndDate = etEnd; + + claimActivity.etDiagnosis = etDiagnosis; + claimActivity.etDiagnosis1 = etDiagnosis1; + claimActivity.etDiagnosis2 = etDiagnosis2; + claimActivity.etDiagnosis3 = etDiagnosis3; + claimActivity.etDiagnosis4 = etDiagnosis4; + + claimActivity.etVisitType = etVisitType; + claimActivity.etPatientCondition = patientCondition; + claimActivity.etPreAuthorization = etPreAuth; + + ArrayList> fakeItems = new ArrayList<>(); + HashMap item1 = new HashMap<>(); + item1.put("Code", "ITEM001"); + item1.put("Price", "50"); + item1.put("Quantity", "1"); + fakeItems.add(item1); + claimActivity.lvItemList = fakeItems; + + ArrayList> fakeServices = new ArrayList<>(); + HashMap svc1 = new HashMap<>(); + svc1.put("Code", "SVC001"); + svc1.put("Price", "40"); + svc1.put("Quantity", "1"); + svc1.put("PackageType", "P"); + svc1.put("SubServicesItems", "[]"); + fakeServices.add(svc1); + claimActivity.lvServiceList = fakeServices; + + doNothing().when(sqlHandler).saveClaim(any(ContentValues.class), anyList(), anyList()); + + boolean result = claimActivity.saveClaim(); + + assertTrue(result); + verify(sqlHandler, times(1)) + .saveClaim(any(ContentValues.class), anyList(), anyList()); + } + +} diff --git a/claimManagement/src/test/java/org/openimis/imisclaims/ClaimManagementActivityTest.java b/claimManagement/src/test/java/org/openimis/imisclaims/ClaimManagementActivityTest.java deleted file mode 100644 index 31b92010..00000000 --- a/claimManagement/src/test/java/org/openimis/imisclaims/ClaimManagementActivityTest.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.openimis.imisclaims; - -import junit.framework.TestCase; - -public class ClaimManagementActivityTest extends TestCase { - -} \ No newline at end of file From 16594fbc859bfda547427c61f90fb6432967b269 Mon Sep 17 00:00:00 2001 From: paul sandjong Date: Thu, 11 Dec 2025 10:30:07 +0100 Subject: [PATCH 02/10] add pull_request on workflow --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c20ef1e7..c8b7d201 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,6 +6,10 @@ on: - '*' # tags: # - '!v*' + pull_request: + branches: + - '*' + types: [opened, synchronize, reopened] jobs: build: From 55d107b91cd2ec1fa21217b6b0f5459b950a103d Mon Sep 17 00:00:00 2001 From: paul sandjong Date: Thu, 8 Jan 2026 13:59:36 +0100 Subject: [PATCH 03/10] Unit tests for SynchronizeService --- .gitignore | 3 + .../imisclaims/SynchronizeService.java | 12 +- .../imisclaims/SynchronizeServiceTest.java | 201 ++++++++++++++++++ 3 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java diff --git a/.gitignore b/.gitignore index 9fb66257..47792f4d 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ proguard/ # Log Files *.log +# Java heap dump files +*.hprof + # Android Studio Navigation editor temp files .navigation/ diff --git a/claimManagement/src/main/java/org/openimis/imisclaims/SynchronizeService.java b/claimManagement/src/main/java/org/openimis/imisclaims/SynchronizeService.java index f7593030..0619ff37 100644 --- a/claimManagement/src/main/java/org/openimis/imisclaims/SynchronizeService.java +++ b/claimManagement/src/main/java/org/openimis/imisclaims/SynchronizeService.java @@ -54,9 +54,9 @@ public class SynchronizeService extends JobIntentService { private static final String claimResponseLine = "[%s] %s"; - private Global global; - private SQLHandler sqlHandler; - private StorageManager storageManager; + protected Global global; + protected SQLHandler sqlHandler; + protected StorageManager storageManager; @Override public void onCreate() { @@ -96,7 +96,7 @@ protected void onHandleWork(@NonNull Intent intent) { } } - private void handleUploadClaims() { + protected void handleUploadClaims() { if (!global.isNetworkAvailable()) { broadcastError(getResources().getString(R.string.CheckInternet), ACTION_UPLOAD_CLAIMS); return; @@ -118,7 +118,7 @@ private void handleUploadClaims() { } } - private JSONArray processClaimResponse(List results) { + protected JSONArray processClaimResponse(List results) { JSONArray jsonResults = new JSONArray(); String date = AppInformation.DateTimeInfo.getDefaultIsoDatetimeFormatter().format(new Date()); for (PostNewClaims.Result result : results) { @@ -252,7 +252,7 @@ private Uri createClaimExportZip(ArrayList exportedClaims) { zipFile); } - private void handleGetClaimCount() { + protected void handleGetClaimCount() { JSONObject counts = sqlHandler.getClaimCounts(); int enteredCount = counts.optInt(SQLHandler.CLAIM_UPLOAD_STATUS_ENTERED, 0); diff --git a/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java b/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java new file mode 100644 index 00000000..37c7eee3 --- /dev/null +++ b/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java @@ -0,0 +1,201 @@ +package org.openimis.imisclaims; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.openimis.imisclaims.usecase.PostNewClaims; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import org.openimis.imisclaims.tools.StorageManager; + +import java.util.Arrays; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = 28, manifest = Config.NONE) +public class SynchronizeServiceTest { + + @Mock private Global global; + @Mock private SQLHandler sqlHandler; + @Mock private Resources resources; + @Mock private Context context; + @Mock private StorageManager storageManager; + + private SynchronizeService synchronizeService; + + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + + synchronizeService = new SynchronizeService() { + @Override + public Context getApplicationContext() { + return context; + } + + @Override + public Resources getResources() { + return resources; + } + + @Override + public void sendBroadcast(Intent intent) { + // override to avoid real broadcast and capture intents if needed + capturedIntent = intent; + } + }; + + synchronizeService.global = global; + synchronizeService.sqlHandler = sqlHandler; + synchronizeService.storageManager = storageManager; + + when(context.getResources()).thenReturn(resources); + when(resources.getString(anyInt())).thenReturn("Error message"); + } + + private Intent capturedIntent; + + // ---------------------------------- + // Tests handleUploadClaims + // ---------------------------------- + + @Test + public void handleUploadClaims_WhenNoNetwork_BroadcastsError() { + when(global.isNetworkAvailable()).thenReturn(false); + + synchronizeService.handleUploadClaims(); + + verify(global).isNetworkAvailable(); + assertNotNull(capturedIntent); + assertEquals(SynchronizeService.ACTION_SYNC_ERROR, capturedIntent.getAction()); + assertEquals("Error message", capturedIntent.getStringExtra(SynchronizeService.EXTRA_ERROR_MESSAGE)); + } + + @Test + public void handleUploadClaims_WhenNoPendingClaims_BroadcastsNoClaimError() throws JSONException { + when(global.isNetworkAvailable()).thenReturn(true); + when(sqlHandler.getAllPendingClaims()).thenReturn(new JSONArray()); + + synchronizeService.handleUploadClaims(); + + verify(sqlHandler).getAllPendingClaims(); + assertNotNull(capturedIntent); + assertEquals(SynchronizeService.ACTION_SYNC_ERROR, capturedIntent.getAction()); + assertEquals("Error message", capturedIntent.getStringExtra(SynchronizeService.EXTRA_ERROR_MESSAGE)); + } + + // Note : we can't test PostNewClaims here without refactoring + // We just test that the method doesn't crash with pending claims + @Test + public void handleUploadClaims_WithPendingClaims_DoesNotCrash() throws JSONException { + when(global.isNetworkAvailable()).thenReturn(true); + + JSONArray pendingClaims = new JSONArray(); + JSONObject claim = new JSONObject(); + claim.put("id", "claim123"); + pendingClaims.put(claim); + + when(sqlHandler.getAllPendingClaims()).thenReturn(pendingClaims); + + synchronizeService.handleUploadClaims(); + + verify(sqlHandler).getAllPendingClaims(); + } + + // ---------------------------------- + // Tests processClaimResponse + // ---------------------------------- + + @Test + public void processClaimResponse_WithRejectedClaim_UpdatesStatusToRejected() throws JSONException { + PostNewClaims.Result rejectedResult = + new PostNewClaims.Result("claim123", PostNewClaims.Result.Status.REJECTED, "Claim rejected"); + + when(sqlHandler.getClaimUUIDForCode("claim123")).thenReturn("uuid123"); + + JSONArray result = synchronizeService.processClaimResponse(Arrays.asList(rejectedResult)); + + verify(sqlHandler).insertClaimUploadStatus( + eq("uuid123"), + anyString(), + eq(SQLHandler.CLAIM_UPLOAD_STATUS_REJECTED), + isNull() + ); + + assertEquals(1, result.length()); + assertTrue(result.getString(0).contains("Claim rejected")); + } + + @Test + public void processClaimResponse_WithError_UpdatesStatusToError() throws JSONException { + PostNewClaims.Result errorResult = + new PostNewClaims.Result("claim123", PostNewClaims.Result.Status.ERROR, "Server error"); + + when(sqlHandler.getClaimUUIDForCode("claim123")).thenReturn("uuid123"); + + JSONArray result = synchronizeService.processClaimResponse(Arrays.asList(errorResult)); + + verify(sqlHandler).insertClaimUploadStatus( + eq("uuid123"), + anyString(), + eq(SQLHandler.CLAIM_UPLOAD_STATUS_ERROR), + eq("Server error") + ); + + assertEquals(1, result.length()); + } + + @Test + public void processClaimResponse_WithSuccess_UpdatesStatusToAccepted() throws JSONException { + PostNewClaims.Result successResult = + new PostNewClaims.Result("claim123", PostNewClaims.Result.Status.SUCCESS, null); + + when(sqlHandler.getClaimUUIDForCode("claim123")).thenReturn("uuid123"); + + JSONArray result = synchronizeService.processClaimResponse(Arrays.asList(successResult)); + + verify(sqlHandler).insertClaimUploadStatus( + eq("uuid123"), + anyString(), + eq(SQLHandler.CLAIM_UPLOAD_STATUS_ACCEPTED), + isNull() + ); + + assertEquals(0, result.length()); // no error message for SUCCESS + } + + // ---------------------------------- + // Test handleGetClaimCount + // ---------------------------------- + + @Test + public void handleGetClaimCount_BroadcastsCorrectCounts() throws JSONException { + JSONObject counts = new JSONObject(); + counts.put(SQLHandler.CLAIM_UPLOAD_STATUS_ENTERED, 5); + counts.put(SQLHandler.CLAIM_UPLOAD_STATUS_ACCEPTED, 3); + counts.put(SQLHandler.CLAIM_UPLOAD_STATUS_REJECTED, 2); + + when(sqlHandler.getClaimCounts()).thenReturn(counts); + + synchronizeService.handleGetClaimCount(); + + assertNotNull(capturedIntent); + assertEquals(SynchronizeService.ACTION_CLAIM_COUNT_RESULT, capturedIntent.getAction()); + assertEquals(5, capturedIntent.getIntExtra(SynchronizeService.EXTRA_CLAIM_COUNT_ENTERED, 0)); + assertEquals(3, capturedIntent.getIntExtra(SynchronizeService.EXTRA_CLAIM_COUNT_ACCEPTED, 0)); + assertEquals(2, capturedIntent.getIntExtra(SynchronizeService.EXTRA_CLAIM_COUNT_REJECTED, 0)); + } +} From 550aa56eaeda03b41d1a5c6d2eb31331c51d255c Mon Sep 17 00:00:00 2001 From: paul sandjong Date: Fri, 9 Jan 2026 12:21:09 +0100 Subject: [PATCH 04/10] Unit tests for madatory fields for saveClaim and claims restored --- .../openimis/imisclaims/ClaimActivity.java | 4 +- .../imisclaims/SynchronizeService.java | 10 +- .../imisclaims/ClaimActivityTest.java | 339 +++++++++++++----- .../imisclaims/SynchronizeServiceTest.java | 1 + 4 files changed, 261 insertions(+), 93 deletions(-) diff --git a/claimManagement/src/main/java/org/openimis/imisclaims/ClaimActivity.java b/claimManagement/src/main/java/org/openimis/imisclaims/ClaimActivity.java index 70990e8e..171629a8 100644 --- a/claimManagement/src/main/java/org/openimis/imisclaims/ClaimActivity.java +++ b/claimManagement/src/main/java/org/openimis/imisclaims/ClaimActivity.java @@ -712,7 +712,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { } } - private boolean isValidData() { + protected boolean isValidData() { if (etHealthFacility.getText().length() == 0) { showValidationDialog(etHealthFacility, getResources().getString(R.string.MissingHealthFacility)); @@ -793,7 +793,7 @@ private boolean isValidData() { return true; } - private boolean isValidInsureeNumber() { + protected boolean isValidInsureeNumber() { Escape escape = new Escape(); return escape.CheckCHFID(etInsureeNumber.getText().toString()); } diff --git a/claimManagement/src/main/java/org/openimis/imisclaims/SynchronizeService.java b/claimManagement/src/main/java/org/openimis/imisclaims/SynchronizeService.java index 0619ff37..ce198bba 100644 --- a/claimManagement/src/main/java/org/openimis/imisclaims/SynchronizeService.java +++ b/claimManagement/src/main/java/org/openimis/imisclaims/SynchronizeService.java @@ -57,6 +57,7 @@ public class SynchronizeService extends JobIntentService { protected Global global; protected SQLHandler sqlHandler; protected StorageManager storageManager; + protected PostNewClaims postNewClaims; @Override public void onCreate() { @@ -66,6 +67,10 @@ public void onCreate() { storageManager = StorageManager.of(this); } + public void setPostNewClaims(PostNewClaims postNewClaims) { + this.postNewClaims = postNewClaims; + } + public static void uploadClaims(Context context) { Intent intent = new Intent(); intent.setAction(ACTION_UPLOAD_CLAIMS); @@ -109,7 +114,10 @@ protected void handleUploadClaims() { } try { - List results = new PostNewClaims().execute(PendingClaim.fromJson(claims)); + if (postNewClaims == null) { + postNewClaims = new PostNewClaims(); + } + List results = postNewClaims.execute(PendingClaim.fromJson(claims)); JSONArray claimStatus = processClaimResponse(results); broadcastSyncSuccess(claimStatus); } catch (Exception e) { diff --git a/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java b/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java index 1255f9ba..79a480ee 100644 --- a/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java +++ b/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java @@ -1,121 +1,80 @@ package org.openimis.imisclaims; +import org.openimis.imisclaims.domain.entity.Claim; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import android.app.Activity; import android.content.ContentValues; -import android.content.res.Resources; -import android.widget.AutoCompleteTextView; -import android.widget.CheckBox; -import android.widget.EditText; import android.text.SpannableStringBuilder; +import android.view.View; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.openimis.imisclaims.tools.StorageManager; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import java.util.ArrayList; import java.util.HashMap; +import java.util.Date; +import java.util.List; @RunWith(RobolectricTestRunner.class) +@Config(sdk = 28, manifest = Config.NONE) public class ClaimActivityTest { - @Mock - Global global; - - @Mock - SQLHandler sqlHandler; - - @Mock - Activity activity; - - @Mock - Resources resources; - - @Mock - StorageManager storageManager; - - ClaimActivity claimActivity; + + ClaimActivity activity; @Before public void setup() { MockitoAnnotations.openMocks(this); - when(activity.getResources()).thenReturn(resources); - when(resources.getString(anyInt())).thenReturn("mockString"); - claimActivity = Robolectric.buildActivity(ClaimActivity.class).create().start().resume().visible().get(); - claimActivity.sqlHandler = sqlHandler; - claimActivity.global = global; - when(global.isNetworkAvailable()).thenReturn(true); // or false, it doesn't matter - } - @Test - public void testSaveClaim_ShouldInsertInDatabase() { - EditText etHF = mock(EditText.class); - EditText etClaimAdmin = mock(EditText.class); - EditText etClaimCode = mock(EditText.class); - EditText etGuaranteeNo = mock(EditText.class); - EditText etCHFID = mock(EditText.class); - EditText etStart = mock(EditText.class); - EditText etEnd = mock(EditText.class); - - AutoCompleteTextView etDiagnosis = mock(AutoCompleteTextView.class); - AutoCompleteTextView etDiagnosis1 = mock(AutoCompleteTextView.class); - AutoCompleteTextView etDiagnosis2 = mock(AutoCompleteTextView.class); - AutoCompleteTextView etDiagnosis3 = mock(AutoCompleteTextView.class); - AutoCompleteTextView etDiagnosis4 = mock(AutoCompleteTextView.class); - - when(etHF.getText()).thenReturn(new SpannableStringBuilder("HF001")); - when(etClaimAdmin.getText()).thenReturn(new SpannableStringBuilder("ADMIN001")); - when(etClaimCode.getText()).thenReturn(new SpannableStringBuilder("CLM001")); - when(etGuaranteeNo.getText()).thenReturn(new SpannableStringBuilder("GNT001")); - when(etCHFID.getText()).thenReturn(new SpannableStringBuilder("INS12345")); - when(etStart.getText()).thenReturn(new SpannableStringBuilder("2025-12-01")); - when(etEnd.getText()).thenReturn(new SpannableStringBuilder("2025-12-09")); - when(etDiagnosis.getText()).thenReturn(new SpannableStringBuilder("A01")); - when(etDiagnosis1.getText()).thenReturn(new SpannableStringBuilder("B01")); - when(etDiagnosis2.getText()).thenReturn(new SpannableStringBuilder("C01")); - when(etDiagnosis3.getText()).thenReturn(new SpannableStringBuilder("D01")); - when(etDiagnosis4.getText()).thenReturn(new SpannableStringBuilder("E01")); - - AutoCompleteTextView etVisitType = mock(AutoCompleteTextView.class); - AutoCompleteTextView patientCondition = mock(AutoCompleteTextView.class); - when(etVisitType.getTag()).thenReturn("O"); - when(patientCondition.getTag()).thenReturn("A"); - - CheckBox etPreAuth = mock(CheckBox.class); - when(etPreAuth.isChecked()).thenReturn(false); - - claimActivity.etHealthFacility = etHF; - claimActivity.etClaimAdmin = etClaimAdmin; - claimActivity.etClaimCode = etClaimCode; - claimActivity.etGuaranteeNo = etGuaranteeNo; - claimActivity.etInsureeNumber = etCHFID; - claimActivity.etStartDate = etStart; - claimActivity.etEndDate = etEnd; - - claimActivity.etDiagnosis = etDiagnosis; - claimActivity.etDiagnosis1 = etDiagnosis1; - claimActivity.etDiagnosis2 = etDiagnosis2; - claimActivity.etDiagnosis3 = etDiagnosis3; - claimActivity.etDiagnosis4 = etDiagnosis4; - - claimActivity.etVisitType = etVisitType; - claimActivity.etPatientCondition = patientCondition; - claimActivity.etPreAuthorization = etPreAuth; + activity = Robolectric.buildActivity(ClaimActivity.class) + .create() + .start() + .resume() + .get(); + + // Inject mocks + activity.sqlHandler = mock(SQLHandler.class); + activity.global = mock(Global.class); + + when(activity.global.isNetworkAvailable()).thenReturn(true); + when(activity.sqlHandler.getAdjustability(anyString())).thenReturn("M"); + + // initialize static lists + ClaimActivity.lvItemList = new ArrayList<>(); + ClaimActivity.lvServiceList = new ArrayList<>(); + // set mandatory fields + activity.etHealthFacility.setText("HF001"); + activity.etClaimAdmin.setText("ADMIN001"); + activity.etClaimCode.setText("CLM001"); + activity.etInsureeNumber.setText("CHF12345"); + activity.etStartDate.setText("2025-01-01"); + activity.etEndDate.setText("2025-01-02"); + activity.etDiagnosis.setText("A01"); + + activity.etVisitType.setText("Other"); + activity.etVisitType.setTag("O"); + + activity.etPatientCondition.setText("Healed"); + activity.etPatientCondition.setTag("H"); + + activity.tvItemTotal.setText("1"); + activity.tvServiceTotal.setText("0"); + + // add fake items and services to avoid MissingClaim ArrayList> fakeItems = new ArrayList<>(); HashMap item1 = new HashMap<>(); item1.put("Code", "ITEM001"); item1.put("Price", "50"); item1.put("Quantity", "1"); fakeItems.add(item1); - claimActivity.lvItemList = fakeItems; + ClaimActivity.lvItemList = fakeItems; ArrayList> fakeServices = new ArrayList<>(); HashMap svc1 = new HashMap<>(); @@ -125,15 +84,215 @@ public void testSaveClaim_ShouldInsertInDatabase() { svc1.put("PackageType", "P"); svc1.put("SubServicesItems", "[]"); fakeServices.add(svc1); - claimActivity.lvServiceList = fakeServices; + ClaimActivity.lvServiceList = fakeServices; + } - doNothing().when(sqlHandler).saveClaim(any(ContentValues.class), anyList(), anyList()); + /* ===================================================== + isValidData() + ===================================================== */ + + @Test + public void isValidData_AllValid_ReturnsTrue() { + boolean result = activity.isValidData(); + assertTrue(result); + } + + @Test + public void isValidData_MissingHealthFacility_ReturnsFalse() { + activity.etHealthFacility.setText(""); + + boolean result = activity.isValidData(); + + assertFalse(result); + } + + @Test + public void isValidData_MissingClaimAdmin_WhenMandatory_ReturnsFalse() { + when(activity.sqlHandler.getAdjustability("ClaimAdministrator")) + .thenReturn("M"); - boolean result = claimActivity.saveClaim(); + activity.etClaimAdmin.setText(""); + + boolean result = activity.isValidData(); + + assertFalse(result); + } + + @Test + public void isValidData_MissingClaimCode_ReturnsFalse() { + activity.etClaimCode.setText(""); + + boolean result = activity.isValidData(); + + assertFalse(result); + } + + @Test + public void isValidData_MissingInsureeNumber_ReturnsFalse() { + activity.etInsureeNumber.setText(""); + + boolean result = activity.isValidData(); + + assertFalse(result); + } + + @Test + public void isValidData_InvalidInsureeNumber_ReturnsFalse() { + activity.etInsureeNumber.setText(""); + Escape escape = mock(Escape.class); + when(escape.CheckCHFID(anyString())).thenReturn(false); // Mock Escape.CheckCHFID to return false + boolean result = activity.isValidData(); + + assertFalse(result); + } + + @Test + public void isValidData_MissingStartDate_ReturnsFalse() { + activity.etStartDate.setText(""); + + boolean result = activity.isValidData(); + + assertFalse(result); + } + + @Test + public void isValidData_MissingEndDate_ReturnsFalse() { + activity.etEndDate.setText(""); + + boolean result = activity.isValidData(); + + assertFalse(result); + } + + @Test + public void isValidData_MissingDiagnosis_ReturnsFalse() { + activity.etDiagnosis.setText(""); + + boolean result = activity.isValidData(); + + assertFalse(result); + } + + @Test + public void isValidData_MissingVisitType_ReturnsFalse() { + activity.etVisitType.setText(""); + activity.etVisitType.setTag(""); + + boolean result = activity.isValidData(); + + assertFalse(result); + } + + @Test + public void isValidData_NoItemsAndServices_ReturnsFalse() { + ClaimActivity.lvItemList.clear(); + ClaimActivity.lvServiceList.clear(); + activity.tvItemTotal.setText("0"); + activity.tvServiceTotal.setText("0"); + + boolean result = activity.isValidData(); + + assertFalse(result); + } + + /* ===================================================== + saveClaim() + ===================================================== */ + + @Test + public void saveClaim_ValidData_CallsSqlHandlerAndReturnsTrue() { + doNothing().when(activity.sqlHandler) + .saveClaim(any(ContentValues.class), anyList(), anyList()); + + boolean result = activity.saveClaim(); assertTrue(result); - verify(sqlHandler, times(1)) + verify(activity.sqlHandler, times(1)) .saveClaim(any(ContentValues.class), anyList(), anyList()); } + @Test + public void saveClaim_ContentValuesContainExpectedData() { + doNothing().when(activity.sqlHandler) + .saveClaim(any(ContentValues.class), anyList(), anyList()); + + activity.saveClaim(); + + verify(activity.sqlHandler).saveClaim( + argThat(cv -> + cv.getAsString("HFCode").equals("HF001") && + cv.getAsString("ClaimCode").equals("CLM001") && + cv.getAsString("VisitType").equals("O") && + cv.getAsInteger("PreAuthorization") == 0 + ), + anyList(), + anyList() + ); + } + + /* ===================================================== + fillClaimFromRestore() + ===================================================== */ + + @Test + public void fillClaimFromRestore_FillsAllFieldsCorrectly() throws Exception { + // Build a complete claim + Claim.Medication med = new Claim.Medication( + "MED1", "Paracetamol", 50.0, "USD", "2", null, null, null, null, null + ); + + Claim.Service svc = new Claim.Service( + "SVC1", "Consultation", 100.0, "USD", "1", null, null, null, null, null + ); + + Claim claim = new Claim( + "UUID123", + "HF001", + "Health Facility", + "INS001", + "John Doe", + "CLM123", + new Date(), + new Date(), + new Date(), + "E", + Claim.Status.ENTERED, + "A01", + "B02", + null, + null, + null, + 150.0, + 150.0, + null, + null, + "G001", + List.of(svc), + List.of(med) + ); + + // Mock global for the method + when(activity.global.getOfficerCode()).thenReturn("ADMIN001"); + when(activity.global.getOfficerHealthFacility()).thenReturn("HF001"); + + // Call the private method using reflection + java.lang.reflect.Method method = ClaimActivity.class + .getDeclaredMethod("fillClaimFromRestore", Claim.class); + method.setAccessible(true); + method.invoke(activity, claim); + + // Verify that UI fields were filled correctly + assertEquals("@CLM123", activity.etClaimCode.getText().toString()); // @ is the mocks resource string to replace "Restored" + assertEquals("ADMIN001", activity.etClaimAdmin.getText().toString()); + assertEquals("HF001", activity.etHealthFacility.getText().toString()); + assertEquals("G001", activity.etGuaranteeNo.getText().toString()); + assertEquals("", activity.etInsureeNumber.getText().toString()); // because Status != REJECTED + assertEquals("", activity.etDiagnosis.getText().toString()); + + // Verify that the lists were populated + assertEquals(1, ClaimActivity.lvItemList.size()); + assertEquals(1, ClaimActivity.lvServiceList.size()); + assertEquals(2, activity.TotalItemService); // 1 item + 1 service + } + } diff --git a/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java b/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java index 37c7eee3..a0ee116e 100644 --- a/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java +++ b/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java @@ -81,6 +81,7 @@ public void handleUploadClaims_WhenNoNetwork_BroadcastsError() { verify(global).isNetworkAvailable(); assertNotNull(capturedIntent); assertEquals(SynchronizeService.ACTION_SYNC_ERROR, capturedIntent.getAction()); + System.out.println("Error Message: " + capturedIntent.getStringExtra(SynchronizeService.EXTRA_ERROR_MESSAGE)); assertEquals("Error message", capturedIntent.getStringExtra(SynchronizeService.EXTRA_ERROR_MESSAGE)); } From b9753e2fa92e2c9b230a4eb250e6b335916bca3f Mon Sep 17 00:00:00 2001 From: paul sandjong Date: Fri, 9 Jan 2026 13:08:31 +0100 Subject: [PATCH 05/10] fix java heap space on runners --- .../test/java/org/openimis/imisclaims/ClaimActivityTest.java | 1 - .../java/org/openimis/imisclaims/SynchronizeServiceTest.java | 1 - gradle.properties | 4 +++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java b/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java index 79a480ee..f6a90b1d 100644 --- a/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java +++ b/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java @@ -23,7 +23,6 @@ import java.util.List; @RunWith(RobolectricTestRunner.class) -@Config(sdk = 28, manifest = Config.NONE) public class ClaimActivityTest { ClaimActivity activity; diff --git a/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java b/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java index a0ee116e..4f154a09 100644 --- a/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java +++ b/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java @@ -25,7 +25,6 @@ import java.util.Arrays; @RunWith(RobolectricTestRunner.class) -@Config(sdk = 28, manifest = Config.NONE) public class SynchronizeServiceTest { @Mock private Global global; diff --git a/gradle.properties b/gradle.properties index 5465fec0..da25f989 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,4 @@ android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file +android.useAndroidX=true +org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.parallel=false \ No newline at end of file From 7b4054a4b7bf4856f55fb3aec81984a28edb5dfb Mon Sep 17 00:00:00 2001 From: paul sandjong Date: Wed, 14 Jan 2026 08:45:06 +0100 Subject: [PATCH 06/10] Units tests for FetchClaims --- .../imisclaims/SynchronizeServiceTest.java | 88 ++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java b/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java index 4f154a09..1d003649 100644 --- a/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java +++ b/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java @@ -16,14 +16,19 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.openimis.imisclaims.domain.entity.Claim; +import org.openimis.imisclaims.usecase.FetchClaims; +import java.util.List; +import java.util.Arrays; +import java.util.Date; + + import org.openimis.imisclaims.usecase.PostNewClaims; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.openimis.imisclaims.tools.StorageManager; -import java.util.Arrays; - @RunWith(RobolectricTestRunner.class) public class SynchronizeServiceTest { @@ -198,4 +203,83 @@ public void handleGetClaimCount_BroadcastsCorrectCounts() throws JSONException { assertEquals(3, capturedIntent.getIntExtra(SynchronizeService.EXTRA_CLAIM_COUNT_ACCEPTED, 0)); assertEquals(2, capturedIntent.getIntExtra(SynchronizeService.EXTRA_CLAIM_COUNT_REJECTED, 0)); } + + @Test + public void testDownloadClaims_WhenNetworkAvailable_ReturnsClaimsList() throws Exception { + // Given + when(global.isNetworkAvailable()).thenReturn(true); + + // Create a mock claim + Claim.Service mockService = new Claim.Service( + "SERVICE1", "Service 1", 100.0, "$", + "1", "1", null, null, null, null); + Claim.Medication mockMedication = new Claim.Medication( + "MED1", "Medication 1", 50.0, "$", + "2", "2", null, null, null, null); + + Claim mockClaim = new Claim( + "claim-uuid-123", "HF001", "Health Facility 1", + "INS123", "John Doe", "CLAIM-001", + new Date(), new Date(), new Date(), + "O", Claim.Status.PROCESSED, "A01", + null, null, null, null, + 150.0, 150.0, null, null, "G123", + Arrays.asList(mockService), + Arrays.asList(mockMedication) + ); + + // Mock the FetchClaims class + FetchClaims fetchClaims = mock(FetchClaims.class); + when(fetchClaims.execute( + eq(global.getOfficerCode()), + any(Claim.Status.class), + any(Date.class), + any(Date.class), + any(Date.class), + any(Date.class) + )).thenReturn(Arrays.asList(mockClaim)); + + // When + List claims = fetchClaims.execute( + global.getOfficerCode(), + Claim.Status.PROCESSED, + new Date(), + new Date(), + new Date(), + new Date() + ); + + // Then + assertNotNull(claims); + assertFalse(claims.isEmpty()); + assertEquals(1, claims.size()); + assertEquals("CLAIM-001", claims.get(0).getClaimNumber()); + assertEquals("John Doe", claims.get(0).getPatientName()); + assertEquals(Claim.Status.PROCESSED, claims.get(0).getStatus()); + + // Verify the service and medication were included + assertFalse(claims.get(0).getServices().isEmpty()); + assertFalse(claims.get(0).getMedications().isEmpty()); + assertEquals("SERVICE1", claims.get(0).getServices().get(0).getCode()); + assertEquals("MED1", claims.get(0).getMedications().get(0).getCode()); + } + + @Test + public void testDownloadClaims_WhenNetworkUnavailable_ThrowsException() { + // Given + when(global.isNetworkAvailable()).thenReturn(false); + + // When/Then + assertThrows(Exception.class, () -> { + // This should fail because there's no network + new FetchClaims().execute( + global.getOfficerCode(), + Claim.Status.PROCESSED, + new Date(), + new Date(), + new Date(), + new Date() + ); + }); + } } From 2d26a134ddc3f8b7f1bfeb07f5158e15f2c39332 Mon Sep 17 00:00:00 2001 From: paul sandjong Date: Thu, 15 Jan 2026 22:17:34 +0100 Subject: [PATCH 07/10] fix gradle.properties --- gradle.properties | 2 -- 1 file changed, 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index da25f989..dbb7bf70 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,2 @@ android.enableJetifier=true android.useAndroidX=true -org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -org.gradle.parallel=false \ No newline at end of file From 7a383c02647a2421428ee74f5fbb1c2bb4c08105 Mon Sep 17 00:00:00 2001 From: paul sandjong Date: Sun, 18 Jan 2026 14:51:37 +0100 Subject: [PATCH 08/10] Unit tests for Insuree Inquiry --- .../openimis/imisclaims/EnquireActivity.java | 15 +- .../imisclaims/EnquireActivityTest.java | 198 ++++++++++++++++++ 2 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 claimManagement/src/test/java/org/openimis/imisclaims/EnquireActivityTest.java diff --git a/claimManagement/src/main/java/org/openimis/imisclaims/EnquireActivity.java b/claimManagement/src/main/java/org/openimis/imisclaims/EnquireActivity.java index 7bad7a04..c0886a31 100644 --- a/claimManagement/src/main/java/org/openimis/imisclaims/EnquireActivity.java +++ b/claimManagement/src/main/java/org/openimis/imisclaims/EnquireActivity.java @@ -201,7 +201,7 @@ protected AlertDialog ShowDialog(final TextView tv, String msg) { @SuppressLint({"WrongConstant", "Range"}) @Nullable - private Insuree getDataFromDb(String chfid) { + protected Insuree getDataFromDb(String chfid) { try { SQLiteDatabase db = openOrCreateDatabase(SQLHandler.DB_NAME_DATA, SQLiteDatabase.OPEN_READONLY, null); String[] columns = {"CHFID", "Photo", "InsureeName", "DOB", "Gender", "ProductCode", "ProductName", "ExpiryDate", "Status", "DedType", "Ded1", "Ded2", "Ceiling1", "Ceiling2"}; @@ -271,13 +271,22 @@ private Insuree getDataFromDb(String chfid) { } + protected Insuree createFetchInsureeInquire(String chfid) { + try { + return new FetchInsureeInquire().execute(chfid); + } catch (Exception e) { + Log.e(LOG_TAG, "Fetching online enquire failed", e); + return null; + } + } + @WorkerThread - private void getInsureeInfo() { + protected void getInsureeInfo() { runOnUiThread(this::ClearForm); String chfid = etCHFID.getText().toString(); if (global.isNetworkAvailable()) { try { - Insuree insuree = new FetchInsureeInquire().execute(chfid); + Insuree insuree = createFetchInsureeInquire(chfid); runOnUiThread(() -> renderResult(insuree)); } catch (HttpException e) { if (e.getCode() == HttpURLConnection.HTTP_NOT_FOUND) { diff --git a/claimManagement/src/test/java/org/openimis/imisclaims/EnquireActivityTest.java b/claimManagement/src/test/java/org/openimis/imisclaims/EnquireActivityTest.java new file mode 100644 index 00000000..f5a0cec5 --- /dev/null +++ b/claimManagement/src/test/java/org/openimis/imisclaims/EnquireActivityTest.java @@ -0,0 +1,198 @@ +package org.openimis.imisclaims; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import android.graphics.Bitmap; +import android.view.View; +import android.widget.ListAdapter; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.openimis.imisclaims.domain.entity.Insuree; +import org.openimis.imisclaims.domain.entity.Policy; +import org.openimis.imisclaims.usecase.FetchInsureeInquire; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +public class EnquireActivityTest { + + EnquireActivity activity; + + @Before + public void setup() { + MockitoAnnotations.openMocks(this); + + activity = Robolectric.buildActivity(EnquireActivity.class) + .create() + .start() + .resume() + .get(); + + // Inject mocked global + activity.global = mock(Global.class); + when(activity.global.isNetworkAvailable()).thenReturn(true); + + // Default CHFID + activity.etCHFID.setText("CHF123"); + } + + /* ===================================================== + buildEnquireValue() + ===================================================== */ + + @Test + public void buildEnquireValue_WhenNull_ReturnsEmptyString() { + String result = activity.buildEnquireValue(null, R.string.totalVisitsLeft); + assertEquals("", result); + } + + @Test + public void buildEnquireValue_WhenValueProvided_ReturnsFormattedString() { + String result = activity.buildEnquireValue(5, R.string.totalVisitsLeft); + assertEquals("TotalVisitsLeft: 5", result); + } + + /* ===================================================== + renderResult() + ===================================================== */ + + @Test + public void renderResult_WhenInsureeIsNull_ShowsNotFoundDialog() { + EnquireActivity spy = spy(activity); + + spy.renderResult(null); + + verify(spy, atLeastOnce()).showDialog( + activity.getResources().getString(R.string.RecordNotFound) + ); + } + + @Test + public void renderResult_WhenCHFIDMismatch_DoesNothing() { + Insuree insuree = mock(Insuree.class); + when(insuree.getChfId()).thenReturn("DIFFERENT"); + + activity.renderResult(insuree); + + assertEquals( + activity.getResources().getString(R.string.CHFID), + activity.tvCHFID.getText().toString() + ); + } + + @Test + public void renderResult_WhenValidInsuree_PopulatesUI() { + Insuree insuree = mock(Insuree.class); + + when(insuree.getChfId()).thenReturn("CHF123"); + when(insuree.getName()).thenReturn("John Doe"); + when(insuree.getGender()).thenReturn("M"); + when(insuree.getDateOfBirth()).thenReturn(new Date()); + when(insuree.getPhoto()).thenReturn(null); + when(insuree.getPhotoPath()).thenReturn(null); + when(insuree.getPolicies()).thenReturn(Collections.emptyList()); + + activity.renderResult(insuree); + + assertEquals("CHF123", activity.tvCHFID.getText().toString()); + assertEquals("John Doe", activity.tvName.getText().toString()); + assertEquals("M", activity.tvGender.getText().toString()); + assertEquals(View.VISIBLE, activity.ll.getVisibility()); + } + + @Test + public void renderResult_WhenPhotoBytesPresent_DisplaysBitmap() { + Insuree insuree = mock(Insuree.class); + + byte[] fakePhoto = new byte[10]; + + when(insuree.getChfId()).thenReturn("CHF123"); + when(insuree.getName()).thenReturn("John Doe"); + when(insuree.getGender()).thenReturn("M"); + when(insuree.getDateOfBirth()).thenReturn(new Date()); + when(insuree.getPhoto()).thenReturn(fakePhoto); + when(insuree.getPolicies()).thenReturn(Collections.emptyList()); + + activity.renderResult(insuree); + + assertNotNull(activity.iv.getDrawable()); + } + + /* ===================================================== + renderResult() with policies + ===================================================== */ + + @Test + public void renderResult_WithPolicies_PopulatesListView() { + Policy policy = mock(Policy.class); + + when(policy.getCode()).thenReturn("PROD1"); + when(policy.getName()).thenReturn("Basic Cover"); + when(policy.getStatus()).thenReturn(Policy.Status.ACTIVE); + when(policy.getExpiryDate()).thenReturn(new Date()); + when(policy.getDeductibleType()).thenReturn(1.0); + when(policy.getDeductibleIp()).thenReturn(10.0); + when(policy.getCeilingIp()).thenReturn(100.0); + + List policies = new ArrayList<>(); + policies.add(policy); + + Insuree insuree = mock(Insuree.class); + when(insuree.getChfId()).thenReturn("CHF123"); + when(insuree.getName()).thenReturn("John Doe"); + when(insuree.getGender()).thenReturn("M"); + when(insuree.getDateOfBirth()).thenReturn(new Date()); + when(insuree.getPhoto()).thenReturn(null); + when(insuree.getPolicies()).thenReturn(policies); + + activity.renderResult(insuree); + + ListAdapter adapter = activity.lv.getAdapter(); + assertNotNull(adapter); + assertEquals(1, adapter.getCount()); + } + + /* ===================================================== + getInsureeInfo() + ===================================================== */ + + @Test + public void getInsureeInfo_WhenOffline_UsesLocalDb() { + EnquireActivity spy = spy(activity); + + when(spy.global.isNetworkAvailable()).thenReturn(false); + doReturn(null).when(spy).getDataFromDb(anyString()); + + spy.getInsureeInfo(); + + verify(spy).getDataFromDb("CHF123"); + } + + @Test + public void getInsureeInfo_WhenOnline_RecordNotFound_ShowsDialog() throws Exception { + EnquireActivity spy = spy(activity); + + when(spy.global.isNetworkAvailable()).thenReturn(true); + + when(spy.createFetchInsureeInquire(anyString())) + .thenThrow(new org.openimis.imisclaims.network.exception.HttpException( + 404, "Not Found", null, null + )); + + spy.getInsureeInfo(); + + verify(spy, atLeastOnce()).showDialog( + activity.getResources().getString(R.string.RecordNotFound) + ); + } +} From ffe8b1e7f9fabcef725646e15bd9b686cbcdec6d Mon Sep 17 00:00:00 2001 From: paul sandjong Date: Mon, 26 Jan 2026 17:55:32 +0100 Subject: [PATCH 09/10] Unit tests for MapItems class --- claimManagement/build.gradle | 1 + claimManagement/fakedatabase | Bin 0 -> 12288 bytes claimManagement/fakemapping | Bin 0 -> 12288 bytes .../java/org/openimis/imisclaims/Global.java | 9 + .../org/openimis/imisclaims/ISQLHandler.java | 113 +++++++++++ .../org/openimis/imisclaims/MapItems.java | 11 +- .../org/openimis/imisclaims/SQLHandler.java | 13 +- .../org/openimis/imisclaims/MapItemsTest.java | 180 ++++++++++++++++++ .../src/test/resources/robolectric.properties | 6 + 9 files changed, 324 insertions(+), 9 deletions(-) create mode 100644 claimManagement/fakedatabase create mode 100644 claimManagement/fakemapping create mode 100644 claimManagement/src/main/java/org/openimis/imisclaims/ISQLHandler.java create mode 100644 claimManagement/src/test/java/org/openimis/imisclaims/MapItemsTest.java create mode 100644 claimManagement/src/test/resources/robolectric.properties diff --git a/claimManagement/build.gradle b/claimManagement/build.gradle index 8cd81622..79a40982 100644 --- a/claimManagement/build.gradle +++ b/claimManagement/build.gradle @@ -184,6 +184,7 @@ android { testOptions { unitTests { includeAndroidResources = true + returnDefaultValues = true } } } diff --git a/claimManagement/fakedatabase b/claimManagement/fakedatabase new file mode 100644 index 0000000000000000000000000000000000000000..187342904a11658316500a85a8ec560ab53dd03f GIT binary patch literal 12288 zcmeI%y$ZrG5Ww+^6`e#k*Nj@w#TW2{ZZ2v?H;Fbvp<0lu(XtaCU3?TdI8(zX${n zKmY**5I_I{1Q0*~0R#{z7ucvqcQ8-~XZD#j=j4*7$?jyGNsKeaI+!h^NgU~TGMz`d z*wwu(KbXwwINHYjUmo%0W6?nX0R#|0009ILKmY**5I_KdDhRy(=I_$JS7BOaL;wK< c5I_I{1Q0*~0R#|00D(UQnyS{d=iPes0Q_PrM*si- literal 0 HcmV?d00001 diff --git a/claimManagement/fakemapping b/claimManagement/fakemapping new file mode 100644 index 0000000000000000000000000000000000000000..e38b0e39d44552435ebcdd2d892bb28878bff851 GIT binary patch literal 12288 zcmeI%y$ZrG5Ww+^6`lOpTr=vRi!Wf6ZZ2v?H;Fbvp|v1+C*Q-Day6nZLQ%)U|Hwfu zm*MzrA*;nabygpXb8eg-OIMVViPj?0_8Itmhw_{Ht9YD0>Tl7O(d^ptw^ZjMeh~;D zfB*srAbvW2Z claimItems, + @NonNull Iterable claimServices); + + void insertClaim(ContentValues claimDetails, + Iterable claimItems, + Iterable claimServices); + + void updateClaim(ContentValues claimDetails, + Iterable claimItems, + Iterable claimServices); + + void deleteClaim(String claimUUID); + + @Nullable JSONObject getClaim(String claimUUID); + @NonNull JSONArray getAllPendingClaims(); + @NonNull JSONArray getClaimItems(String claimUUID); + @NonNull JSONArray getClaimServices(String claimUUID); + + void insertClaimUploadStatus(@NonNull String claimUUID, + @NonNull String uploadDate, + @NonNull String uploadStatus, + String uploadMessage); + + String getClaimUUIDForCode(@NonNull String claimCode); + @NonNull JSONObject getClaimCounts(); + + // --- Query helpers --- + @NonNull JSONArray getQueryResultAsJsonArray(@NonNull String rawQuery, String[] selectionArgs); + @NonNull JSONArray getQueryResultAsJsonArray(@NonNull String tableName, + String[] columns, + String selection, + String[] selectionArgs); + + // --- Claim infos --- + @NonNull JSONArray getClaimInfo(String selection, String[] selectionArgs); + @NonNull JSONArray getEnteredClaimInfo(); + @NonNull JSONArray getAcceptedClaimInfo(); + @NonNull JSONArray getRejectedClaimInfo(); + + // --- Services & Items --- + String getServiceName(String code); + String getPackageType(String code); + String getServiceId(String code); + String getManualPrice(String code); + + void InsertService(String id, String code, String name, String type, + String price, String packageType, int manualPrice); + + void InsertItem(String id, String code, String name, String type, String price); + void InsertSubServices(String serviceId, String serviceLinked, String qty, String price); + void InsertSubItems(String itemId, String serviceId, String qty, String price); + + JSONArray getSubServicesIds(String id); + JSONArray getSubItemsId(String id); + JSONObject getService(String serviceId); + JSONObject getItem(String itemId); + + // --- Search --- + Cursor SearchDisease(String inputText); + Cursor searchItems(String filter); + Cursor searchServices(String filter); + Cursor SearchHF(String inputText); + + // --- Health facilities --- + void InsertHealthFacilities(String id, String code, String name); + + // --- Lifecycle --- + void closeDatabases(); +} diff --git a/claimManagement/src/main/java/org/openimis/imisclaims/MapItems.java b/claimManagement/src/main/java/org/openimis/imisclaims/MapItems.java index d5f8986c..2687f34b 100644 --- a/claimManagement/src/main/java/org/openimis/imisclaims/MapItems.java +++ b/claimManagement/src/main/java/org/openimis/imisclaims/MapItems.java @@ -39,7 +39,6 @@ public class MapItems extends ImisActivity { ListView lvMapItems; CheckBox chkAll, chk; EditText etSearchItems; - ArrayList> ItemsList = new ArrayList>(); HashMap oItem; @@ -118,12 +117,10 @@ public void BindItemList() { //String[] Items = {"Item1","Item2","Item3","Item4","Item5"}; //SQLHandler sql = new SQLHandler(null, null, null, 3); - SQLHandler sql = new SQLHandler(this); - Cursor c = sql.getMapping("I"); + Cursor c = sqlHandler.getMapping("I"); boolean isMapped = false; - for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) { HashMap item = new HashMap(); item.put("Code", c.getString(0)); @@ -154,7 +151,7 @@ public void BindItemList() { } } - private void CheckUncheckAll(boolean isChecked) { + protected void CheckUncheckAll(boolean isChecked) { for (int i = 0; i < ItemsList.size(); i++) { oItem = (HashMap) ItemsList.get(i); oItem.put("isMapped", isChecked); @@ -224,7 +221,7 @@ public void run() { } } - private int Save() { + protected int Save() { int count = 0; sqlHandler.ClearMapping("I"); for (int i = 0; i < ItemsList.size(); i++) { @@ -258,7 +255,7 @@ public void onClick(DialogInterface dialog, int which) { } - private class ItemAdapter extends SimpleAdapter { + protected class ItemAdapter extends SimpleAdapter { private ArrayList> OriginalList, FilteredList; private ArrayList> ItemList; diff --git a/claimManagement/src/main/java/org/openimis/imisclaims/SQLHandler.java b/claimManagement/src/main/java/org/openimis/imisclaims/SQLHandler.java index 159a045d..c705e41e 100644 --- a/claimManagement/src/main/java/org/openimis/imisclaims/SQLHandler.java +++ b/claimManagement/src/main/java/org/openimis/imisclaims/SQLHandler.java @@ -32,8 +32,8 @@ public class SQLHandler extends SQLiteOpenHelper { public static final String CLAIM_UPLOAD_STATUS_ENTERED = "Entered"; public static final String CLAIM_UPLOAD_STATUS_ARCHIVED = "Archived"; - public static final String DB_NAME_MAPPING = Global.getGlobal().getSubdirectory("Databases") + "/" + "Mapping.db3"; - public static final String DB_NAME_DATA = Global.getGlobal().getSubdirectory("Databases") + "/" + "ImisData.db3"; + public static final String DB_NAME_MAPPING = isRunningTest() ? "fakemapping" : Global.getGlobal().getSubdirectory("Databases") + "/" + "Mapping.db3"; + public static final String DB_NAME_DATA = isRunningTest() ? "fakedatabase" : Global.getGlobal().getSubdirectory("Databases") + "/" + "ImisData.db3"; private static final String CreateTableMapping = "CREATE TABLE IF NOT EXISTS tblMapping(Code TEXT,Name TEXT,Type TEXT);"; private static final String createTablePolicyInquiry = "CREATE TABLE IF NOT EXISTS tblPolicyInquiry(InsureeNumber text,Photo BLOB, InsureeName Text, DOB Text, Gender Text, ProductCode Text, ProductName Text, ExpiryDate Text, Status Text, DedType Int, Ded1 Int, Ded2 Int, Ceiling1 Int, Ceiling2 Int);"; @@ -79,6 +79,15 @@ public void onCreate(SQLiteDatabase db) { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } + public static boolean isRunningTest() { + try { + Class.forName("org.robolectric.RobolectricTestRunner"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + @Nullable public Cursor getMapping(String Type) { try { diff --git a/claimManagement/src/test/java/org/openimis/imisclaims/MapItemsTest.java b/claimManagement/src/test/java/org/openimis/imisclaims/MapItemsTest.java new file mode 100644 index 00000000..584a2e3f --- /dev/null +++ b/claimManagement/src/test/java/org/openimis/imisclaims/MapItemsTest.java @@ -0,0 +1,180 @@ +package org.openimis.imisclaims; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import android.app.AlertDialog; +import android.app.Application; +import androidx.test.core.app.ApplicationProvider; +import android.database.Cursor; +import android.database.MatrixCursor; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.HashMap; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {28}, application = Global.class) +public class MapItemsTest { + + SQLHandler mockSqlHandler; + Cursor mockCursor; + MapItems activity; + + @Before + public void setup() { + // Création des mocks avec la syntaxe moderne + mockSqlHandler = mock(SQLHandler.class); + mockCursor = mock(Cursor.class); + + activity = Robolectric.buildActivity(MapItems.class).create().get(); + activity.sqlHandler = mockSqlHandler; + } + + @Test + public void bindItemList_loadsItemsCorrectly() { + doReturn(mockCursor).when(activity.sqlHandler).getMapping("I"); + when(mockCursor.moveToFirst()).thenReturn(true); + when(mockCursor.isAfterLast()).thenReturn(false, true); + when(mockCursor.getString(0)).thenReturn("ITEM001"); + when(mockCursor.getString(1)).thenReturn("Paracetamol"); + when(mockCursor.getString(2)).thenReturn(null); + + activity.BindItemList(); + + assertEquals(1, activity.ItemsList.size()); + HashMap item = activity.ItemsList.get(0); + assertEquals("ITEM001", item.get("Code")); + assertEquals("Paracetamol", item.get("Name")); + assertFalse((Boolean) item.get("isMapped")); + verify(mockCursor).close(); + } + + @Test + public void clickingItem_togglesIsMapped() { + HashMap item = new HashMap<>(); + item.put("Code", "ITEM001"); + item.put("Name", "Test"); + item.put("isMapped", false); + activity.ItemsList.add(item); + + activity.alAdapter = activity.new ItemAdapter(activity, activity.ItemsList, + R.layout.mappinglist, + new String[]{"Code", "Name", "isMapped"}, + new int[]{R.id.tvMapCode,R.id.tvMapName,R.id.chkMap}); + + activity.lvMapItems.setAdapter(activity.alAdapter); + + activity.lvMapItems.performItemClick(null, 0, 0); + + assertTrue((Boolean) activity.ItemsList.get(0).get("isMapped")); + } + + @Test + public void checkAll_setsAllItemsMapped() { + for (int i = 0; i < 3; i++) { + HashMap item = new HashMap<>(); + item.put("Code", "ITEM00" + i); + item.put("Name", "Item " + i); + item.put("isMapped", false); + activity.ItemsList.add(item); + } + + activity.alAdapter = activity.new ItemAdapter(activity, activity.ItemsList, + R.layout.mappinglist, + new String[]{"Code", "Name", "isMapped"}, + new int[]{R.id.tvMapCode,R.id.tvMapName,R.id.chkMap}); + + activity.CheckUncheckAll(true); + + for (HashMap item : activity.ItemsList) { + assertTrue((Boolean)item.get("isMapped")); + } + } + + @Test + public void save_returnsCorrectCodes() { + HashMap item = new HashMap<>(); + item.put("Code","ITEM001"); + item.put("Name","Test"); + item.put("isMapped",false); + activity.ItemsList.add(item); + + when(mockSqlHandler.InsertMapping(anyString(), anyString(), anyString())).thenReturn(true); + + int result = activity.Save(); + assertEquals(1, result); + + item.put("isMapped", true); + result = activity.Save(); + assertEquals(0, result); + } + + @Test + public void save_returns2_whenInsertFails() { + HashMap item = new HashMap<>(); + item.put("Code","ITEM001"); + item.put("Name","Test"); + item.put("isMapped", true); + activity.ItemsList.add(item); + + when(mockSqlHandler.InsertMapping(anyString(), anyString(), anyString())).thenReturn(false); + doNothing().when(mockSqlHandler).ClearMapping(anyString()); + + + int result = activity.Save(); + assertEquals(2, result); + } + + @Test + public void filter_searchWorksCorrectly() { + HashMap item1 = new HashMap<>(); + item1.put("Code","ITEM001"); + item1.put("Name","Paracetamol"); + item1.put("isMapped", false); + activity.ItemsList.add(item1); + + HashMap item2 = new HashMap<>(); + item2.put("Code","ITEM002"); + item2.put("Name","Aspirin"); + item2.put("isMapped", false); + activity.ItemsList.add(item2); + + activity.alAdapter = activity.new ItemAdapter(activity, activity.ItemsList, + R.layout.mappinglist, + new String[]{"Code","Name","isMapped"}, + new int[]{R.id.tvMapCode,R.id.tvMapName,R.id.chkMap}); + + activity.alAdapter.getFilter().filter("para"); + + assertEquals(1, activity.ItemsList.size()); + assertEquals("Paracetamol", activity.ItemsList.get(0).get("Name")); + } + + @Test + public void adapter_getView_setsCorrectValues() { + HashMap item = new HashMap<>(); + item.put("Code","ITEM001"); + item.put("Name","Test"); + item.put("isMapped", true); + activity.ItemsList.add(item); + + MapItems.ItemAdapter adapter = activity.new ItemAdapter(activity, activity.ItemsList, + R.layout.mappinglist, + new String[]{"Code","Name","isMapped"}, + new int[]{R.id.tvMapCode,R.id.tvMapName,R.id.chkMap}); + + assertNotNull(adapter.getView(0, null, null)); + } + + @Test + public void showDialog_createsAlertDialog() { + AlertDialog dialog = activity.ShowDialog("Test message"); + assertNotNull(dialog); + } +} diff --git a/claimManagement/src/test/resources/robolectric.properties b/claimManagement/src/test/resources/robolectric.properties new file mode 100644 index 00000000..cb053025 --- /dev/null +++ b/claimManagement/src/test/resources/robolectric.properties @@ -0,0 +1,6 @@ +# app/src/test/resources/robolectric.properties + +# this line force Robolectric to use the legacy SQLite implementation +# which is more stable and does not depend on native binaries. +sqliteMode=LEGACY + \ No newline at end of file From c4b18788ac753694f5bde2f7d5e982a09e31aa62 Mon Sep 17 00:00:00 2001 From: paul sandjong Date: Mon, 26 Jan 2026 18:01:44 +0100 Subject: [PATCH 10/10] remove unused ISQLHandler class --- .../org/openimis/imisclaims/ISQLHandler.java | 113 ------------------ 1 file changed, 113 deletions(-) delete mode 100644 claimManagement/src/main/java/org/openimis/imisclaims/ISQLHandler.java diff --git a/claimManagement/src/main/java/org/openimis/imisclaims/ISQLHandler.java b/claimManagement/src/main/java/org/openimis/imisclaims/ISQLHandler.java deleted file mode 100644 index 6ed0c740..00000000 --- a/claimManagement/src/main/java/org/openimis/imisclaims/ISQLHandler.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.openimis.imisclaims; - -import android.content.ContentValues; -import android.database.Cursor; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.util.Locale; - -public interface ISQLHandler { - - // --- Mapping & References --- - @Nullable Cursor getMapping(String type); - boolean InsertMapping(String code, String name, String type); - void InsertReferences(String code, String name, String type, String price); - void ClearMapping(String type); - void ClearReferencesSI(); - String getPrice(String code, String type); - String getItemPrice(String code); - String getServicePrice(String code); - String getReferenceName(@NonNull String referenceCode); - - // --- Controls --- - void InsertControls(String fieldName, String adjustability); - String getAdjustability(String fieldName); - - // --- Claim admins --- - void InsertClaimAdmins(String code, String hfCode, String name); - String getClaimAdminInfo(String code, String column); - - // --- Tables --- - void createTables(); - void createMappingTables(); - boolean checkTableExists(String table); - boolean checkIfAny(String table); - boolean checkIfExists(String table, String whereClause, String... whereArgs); - void ClearAll(String tableName); - - // --- Claims --- - void saveClaim(@NonNull ContentValues claimDetails, - @NonNull Iterable claimItems, - @NonNull Iterable claimServices); - - void insertClaim(ContentValues claimDetails, - Iterable claimItems, - Iterable claimServices); - - void updateClaim(ContentValues claimDetails, - Iterable claimItems, - Iterable claimServices); - - void deleteClaim(String claimUUID); - - @Nullable JSONObject getClaim(String claimUUID); - @NonNull JSONArray getAllPendingClaims(); - @NonNull JSONArray getClaimItems(String claimUUID); - @NonNull JSONArray getClaimServices(String claimUUID); - - void insertClaimUploadStatus(@NonNull String claimUUID, - @NonNull String uploadDate, - @NonNull String uploadStatus, - String uploadMessage); - - String getClaimUUIDForCode(@NonNull String claimCode); - @NonNull JSONObject getClaimCounts(); - - // --- Query helpers --- - @NonNull JSONArray getQueryResultAsJsonArray(@NonNull String rawQuery, String[] selectionArgs); - @NonNull JSONArray getQueryResultAsJsonArray(@NonNull String tableName, - String[] columns, - String selection, - String[] selectionArgs); - - // --- Claim infos --- - @NonNull JSONArray getClaimInfo(String selection, String[] selectionArgs); - @NonNull JSONArray getEnteredClaimInfo(); - @NonNull JSONArray getAcceptedClaimInfo(); - @NonNull JSONArray getRejectedClaimInfo(); - - // --- Services & Items --- - String getServiceName(String code); - String getPackageType(String code); - String getServiceId(String code); - String getManualPrice(String code); - - void InsertService(String id, String code, String name, String type, - String price, String packageType, int manualPrice); - - void InsertItem(String id, String code, String name, String type, String price); - void InsertSubServices(String serviceId, String serviceLinked, String qty, String price); - void InsertSubItems(String itemId, String serviceId, String qty, String price); - - JSONArray getSubServicesIds(String id); - JSONArray getSubItemsId(String id); - JSONObject getService(String serviceId); - JSONObject getItem(String itemId); - - // --- Search --- - Cursor SearchDisease(String inputText); - Cursor searchItems(String filter); - Cursor searchServices(String filter); - Cursor SearchHF(String inputText); - - // --- Health facilities --- - void InsertHealthFacilities(String id, String code, String name); - - // --- Lifecycle --- - void closeDatabases(); -}