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: 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/build.gradle b/claimManagement/build.gradle index c3a245d7..79a40982 100644 --- a/claimManagement/build.gradle +++ b/claimManagement/build.gradle @@ -180,6 +180,13 @@ android { packagingOptions { exclude 'META-INF/DEPENDENCIES' } + + testOptions { + unitTests { + includeAndroidResources = true + returnDefaultValues = true + } + } } apollo { @@ -193,6 +200,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 +229,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/fakedatabase b/claimManagement/fakedatabase new file mode 100644 index 00000000..18734290 Binary files /dev/null and b/claimManagement/fakedatabase differ diff --git a/claimManagement/fakemapping b/claimManagement/fakemapping new file mode 100644 index 00000000..e38b0e39 Binary files /dev/null and b/claimManagement/fakemapping differ diff --git a/claimManagement/src/main/java/org/openimis/imisclaims/ClaimActivity.java b/claimManagement/src/main/java/org/openimis/imisclaims/ClaimActivity.java index e9894971..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()); } @@ -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/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/main/java/org/openimis/imisclaims/Global.java b/claimManagement/src/main/java/org/openimis/imisclaims/Global.java index 05345d19..aa01e284 100644 --- a/claimManagement/src/main/java/org/openimis/imisclaims/Global.java +++ b/claimManagement/src/main/java/org/openimis/imisclaims/Global.java @@ -77,6 +77,15 @@ public class Global extends Application { private Token JWTToken; private String[] permissions; + public static boolean isRunningTest() { + try { + Class.forName("org.robolectric.RobolectricTestRunner"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + @Override public void onCreate() { super.onCreate(); 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/main/java/org/openimis/imisclaims/SynchronizeService.java b/claimManagement/src/main/java/org/openimis/imisclaims/SynchronizeService.java index f7593030..ce198bba 100644 --- a/claimManagement/src/main/java/org/openimis/imisclaims/SynchronizeService.java +++ b/claimManagement/src/main/java/org/openimis/imisclaims/SynchronizeService.java @@ -54,9 +54,10 @@ 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; + 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); @@ -96,7 +101,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; @@ -109,7 +114,10 @@ private 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) { @@ -118,7 +126,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 +260,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/ClaimActivityTest.java b/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java new file mode 100644 index 00000000..f6a90b1d --- /dev/null +++ b/claimManagement/src/test/java/org/openimis/imisclaims/ClaimActivityTest.java @@ -0,0 +1,297 @@ +package org.openimis.imisclaims; +import org.openimis.imisclaims.domain.entity.Claim; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import android.content.ContentValues; +import android.text.SpannableStringBuilder; +import android.view.View; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +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) +public class ClaimActivityTest { + + ClaimActivity activity; + + @Before + public void setup() { + MockitoAnnotations.openMocks(this); + + 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; + + 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; + } + + /* ===================================================== + 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"); + + 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(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/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 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) + ); + } +} 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/java/org/openimis/imisclaims/SynchronizeServiceTest.java b/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java new file mode 100644 index 00000000..1d003649 --- /dev/null +++ b/claimManagement/src/test/java/org/openimis/imisclaims/SynchronizeServiceTest.java @@ -0,0 +1,285 @@ +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.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; + +@RunWith(RobolectricTestRunner.class) +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()); + System.out.println("Error Message: " + capturedIntent.getStringExtra(SynchronizeService.EXTRA_ERROR_MESSAGE)); + 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)); + } + + @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() + ); + }); + } +} 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 diff --git a/gradle.properties b/gradle.properties index 5465fec0..dbb7bf70 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file +android.useAndroidX=true