/*
* This program is part of the OpenLMIS logistics management information
* system platform software.
*
* Copyright © 2015 ThoughtWorks, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. This program is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details. You should
* have received a copy of the GNU Affero General Public License along with
* this program. If not, see http://www.gnu.org/licenses. For additional
* information contact info@OpenLMIS.org
*/
package org.openlmis.core.service;
import android.support.annotation.NonNull;
import com.google.inject.AbstractModule;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openlmis.core.LMISTestRunner;
import org.openlmis.core.exceptions.LMISException;
import org.openlmis.core.manager.MovementReasonManager;
import org.openlmis.core.manager.SharedPreferenceMgr;
import org.openlmis.core.manager.UserInfoMgr;
import org.openlmis.core.model.Cmm;
import org.openlmis.core.model.Product;
import org.openlmis.core.model.ProgramDataForm;
import org.openlmis.core.model.RnRForm;
import org.openlmis.core.model.StockCard;
import org.openlmis.core.model.StockMovementItem;
import org.openlmis.core.model.SyncError;
import org.openlmis.core.model.SyncType;
import org.openlmis.core.model.User;
import org.openlmis.core.model.builder.ProgramDataFormBuilder;
import org.openlmis.core.model.builder.StockCardBuilder;
import org.openlmis.core.model.repository.CmmRepository;
import org.openlmis.core.model.repository.ProductRepository;
import org.openlmis.core.model.repository.ProgramDataFormRepository;
import org.openlmis.core.model.repository.RnrFormRepository;
import org.openlmis.core.model.repository.StockRepository;
import org.openlmis.core.model.repository.SyncErrorsRepository;
import org.openlmis.core.network.LMISRestApi;
import org.openlmis.core.network.model.AppInfoRequest;
import org.openlmis.core.network.model.CmmEntry;
import org.openlmis.core.network.model.StockMovementEntry;
import org.openlmis.core.network.model.SyncUpRequisitionResponse;
import org.openlmis.core.utils.Constants;
import org.openlmis.core.utils.DateUtil;
import org.robolectric.RuntimeEnvironment;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import roboguice.RoboGuice;
import rx.Scheduler;
import rx.android.plugins.RxAndroidPlugins;
import rx.android.plugins.RxAndroidSchedulersHook;
import rx.schedulers.Schedulers;
import static junit.framework.Assert.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyList;
import static org.mockito.Matchers.anyListOf;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.roboguice.shaded.goole.common.collect.Lists.newArrayList;
@RunWith(LMISTestRunner.class)
public class SyncUpManagerTest {
private RnrFormRepository mockedRnrFormRepository;
private SyncErrorsRepository mockedSyncErrorsRepository;
private ProductRepository mockedProductRepository;
private CmmRepository mockedCmmRepository;
private SharedPreferenceMgr mockedSharedPreferenceMgr;
private LMISRestApi mockedLmisRestApi;
private StockRepository stockRepository;
private SyncUpManager syncUpManager;
private ProgramDataFormRepository mockedProgramDataFormRepository;
@Before
public void setup() throws LMISException {
mockedRnrFormRepository = mock(RnrFormRepository.class);
mockedSyncErrorsRepository = mock(SyncErrorsRepository.class);
mockedProductRepository = mock(ProductRepository.class);
mockedCmmRepository = mock(CmmRepository.class);
mockedProgramDataFormRepository = mock(ProgramDataFormRepository.class);
mockedSharedPreferenceMgr = mock(SharedPreferenceMgr.class);
mockedLmisRestApi = mock(LMISRestApi.class);
RoboGuice.overrideApplicationInjector(RuntimeEnvironment.application, new MyTestModule());
syncUpManager = RoboGuice.getInjector(RuntimeEnvironment.application).getInstance(SyncUpManager.class);
syncUpManager.lmisRestApi = mockedLmisRestApi;
stockRepository = RoboGuice.getInjector(RuntimeEnvironment.application).getInstance(StockRepository.class);
User user = new User("user", "123");
user.setFacilityCode("FC1");
user.setFacilityId("123");
UserInfoMgr.getInstance().setUser(user);
RxAndroidPlugins.getInstance().reset();
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
@Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
});
}
@Test
public void shouldSubmitAllUnsyncedRequisitions() throws LMISException, SQLException {
List<RnRForm> unSyncedList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
RnRForm form = new RnRForm();
unSyncedList.add(form);
}
when(mockedRnrFormRepository.queryAllUnsyncedForms()).thenReturn(unSyncedList);
SyncUpRequisitionResponse response = new SyncUpRequisitionResponse();
response.setRequisitionId("1");
when(mockedLmisRestApi.submitRequisition(any(RnRForm.class))).thenReturn(response);
syncUpManager.syncRnr();
verify(mockedLmisRestApi, times(10)).submitRequisition(any(RnRForm.class));
verify(mockedRnrFormRepository, times(10)).createOrUpdateWithItems(any(RnRForm.class));
verify(mockedSyncErrorsRepository, times(10)).deleteBySyncTypeAndObjectId(any(SyncType.class), anyLong());
}
@Test
public void shouldCallEmergencyRequisition() throws Exception {
RnRForm form = new RnRForm();
form.setEmergency(true);
when(mockedRnrFormRepository.queryAllUnsyncedForms()).thenReturn(newArrayList(form));
syncUpManager.syncRnr();
verify(mockedLmisRestApi).submitEmergencyRequisition(any(RnRForm.class));
}
@Test
public void shouldSyncUnSyncedStockMovementData() throws LMISException, SQLException, ParseException {
StockCard stockCard = createTestStockCardData();
doReturn(null).when(mockedLmisRestApi).syncUpStockMovementData(anyString(), anyList());
syncUpManager.syncStockCards();
stockRepository.refresh(stockCard);
List<StockMovementItem> items = newArrayList(stockCard.getForeignStockMovementItems());
assertThat(items.size(), is(2));
assertThat(items.get(0).isSynced(), is(true));
assertThat(items.get(1).isSynced(), is(true));
verify(mockedSyncErrorsRepository).deleteBySyncTypeAndObjectId(any(SyncType.class), anyLong());
}
@Test
public void shouldSaveSyncErrorWhenUnSyncedStockMovementDataFail() throws LMISException, SQLException, ParseException {
createTestStockCardData();
doThrow(new LMISException("mocked exception")).when(mockedLmisRestApi).syncUpStockMovementData(anyString(), anyList());
syncUpManager.syncStockCards();
verify(mockedSyncErrorsRepository).save(any(SyncError.class));
}
@Test
public void shouldNotMarkAsSyncedWhenStockMovementSyncFailed() throws LMISException, ParseException {
StockCard stockCard = createTestStockCardData();
doThrow(new RuntimeException("Sync Failed")).when(mockedLmisRestApi).syncUpStockMovementData(anyString(), anyList());
try {
syncUpManager.syncStockCards();
} catch (RuntimeException e) {
}
stockRepository.refresh(stockCard);
List<StockMovementItem> items = newArrayList(stockCard.getForeignStockMovementItems());
assertThat(items.size(), is(2));
assertThat(items.get(0).isSynced(), is(false));
assertThat(items.get(1).isSynced(), is(false));
}
@NonNull
private StockCard createTestStockCardData() throws LMISException, ParseException {
ProductRepository productRepository = RoboGuice.getInjector(RuntimeEnvironment.application).getInstance(ProductRepository.class);
StockCard stockCard = StockCardBuilder.saveStockCardWithOneMovement(stockRepository);
Product product = new Product();
product.setCode("PD1");
productRepository.createOrUpdate(product);
stockCard.setProduct(product);
stockRepository.createOrUpdate(stockCard);
//ready to sync
StockMovementItem item = new StockMovementItem();
item.setMovementQuantity(100L);
item.setStockOnHand(-1);
item.setMovementDate(DateUtil.today());
item.setMovementType(MovementReasonManager.MovementType.RECEIVE);
item.setStockCard(stockCard);
item.setSynced(false);
stockRepository.addStockMovementAndUpdateStockCard(item);
stockRepository.refresh(stockCard);
return stockCard;
}
@Test
public void shouldSetTypeAndCustomPropsAfterNewStockMovementEntry() throws LMISException, ParseException {
StockCard stockCard = createTestStockCardData();
StockMovementItem stockMovementItem = stockCard.getForeignStockMovementItems().iterator().next();
StockMovementEntry stockMovementEntry = new StockMovementEntry(stockMovementItem, null);
assertEquals(stockMovementEntry.getType(), "ADJUSTMENT");
assertEquals(stockMovementEntry.getCustomProps().get("expirationDates"), stockMovementItem.getStockCard().getExpireDates());
}
@Test
public void shouldSyncAppVersion() throws Exception {
when(mockedSharedPreferenceMgr.hasSyncedVersion()).thenReturn(false);
User user = new User();
UserInfoMgr.getInstance().setUser(user);
syncUpManager.syncAppVersion();
verify(mockedLmisRestApi).updateAppVersion(any(AppInfoRequest.class));
}
@Test
public void shouldSyncArchivedProducts() throws Exception {
List<String> productCodes = new ArrayList<>();
syncUpManager.productRepository = mockedProductRepository;
when(mockedProductRepository.listArchivedProductCodes()).thenReturn(productCodes);
syncUpManager.syncArchivedProducts();
verify(mockedLmisRestApi).syncUpArchivedProducts(UserInfoMgr.getInstance().getUser().getFacilityId(), productCodes);
}
@Test
public void shouldSyncUpRapidTestsData() throws Exception {
ProgramDataForm rapidTest1 = new ProgramDataFormBuilder().build();
rapidTest1.setSynced(false);
rapidTest1.setStatus(ProgramDataForm.STATUS.AUTHORIZED);
ProgramDataForm rapidTest2 = new ProgramDataFormBuilder().build();
rapidTest2.setSynced(true);
rapidTest2.setStatus(ProgramDataForm.STATUS.AUTHORIZED);
syncUpManager.programDataFormRepository = mockedProgramDataFormRepository;
when(mockedProgramDataFormRepository.listByProgramCode(Constants.RAPID_TEST_CODE)).thenReturn(newArrayList(rapidTest1,rapidTest2));
syncUpManager.syncRapidTestForms();
verify(mockedLmisRestApi).syncUpProgramDataForm(rapidTest1);
}
@Test
public void shouldSaveErrorMessageWhenSyncRnRFormFail() throws Exception {
List<RnRForm> unSyncedList = new ArrayList<>();
RnRForm form = new RnRForm();
unSyncedList.add(form);
when(mockedRnrFormRepository.queryAllUnsyncedForms()).thenReturn(unSyncedList);
doThrow(new LMISException("mocked exception")).when(mockedLmisRestApi).submitRequisition(any(RnRForm.class));
syncUpManager.syncRnr();
verify(mockedSyncErrorsRepository).save(any(SyncError.class));
}
@Test
public void shouldNotSyncAppVersion() throws Exception {
when(mockedSharedPreferenceMgr.hasSyncedVersion()).thenReturn(true);
syncUpManager.syncAppVersion();
verify(mockedLmisRestApi, never()).updateAppVersion(any(AppInfoRequest.class));
}
@Test
public void shouldRemoveInvalidItemsFromForms() throws LMISException {
List<RnRForm> unSyncedList = new ArrayList<>();
for (int i = 0; i < 2; i++) {
RnRForm form = new RnRForm();
unSyncedList.add(form);
}
when(mockedRnrFormRepository.queryAllUnsyncedForms()).thenReturn(unSyncedList);
SyncUpRequisitionResponse response = new SyncUpRequisitionResponse();
response.setRequisitionId("1");
when(mockedLmisRestApi.submitRequisition(any(RnRForm.class))).thenReturn(response);
syncUpManager.syncRnr();
verify(mockedRnrFormRepository, times(1)).queryAllUnsyncedForms();
verify(mockedLmisRestApi, times(2)).submitRequisition(any(RnRForm.class));
verify(mockedRnrFormRepository, times(2)).createOrUpdateWithItems(any(RnRForm.class));
verify(mockedSyncErrorsRepository, times(2)).deleteBySyncTypeAndObjectId(any(SyncType.class), anyLong());
}
@Test
public void shouldSyncUpUnSyncedStockCardListWhenHasNotSyncedUpLatestMovementLastDay() throws Exception {
when(mockedSharedPreferenceMgr.hasSyncedUpLatestMovementLastDay()).thenReturn(false);
syncUpManager.syncUpUnSyncedStockCardCodes();
verify(mockedLmisRestApi).syncUpUnSyncedStockCards("123", new ArrayList<String>());
verify(mockedSharedPreferenceMgr).setLastMovementHandShakeDateToToday();
}
@Test
public void shouldRefreshLastSyncStockCardDateWhenHasNoUnSyncedStockCard() throws Exception {
when(mockedSharedPreferenceMgr.hasSyncedUpLatestMovementLastDay()).thenReturn(false);
syncUpManager.syncUpUnSyncedStockCardCodes();
verify(mockedLmisRestApi).syncUpUnSyncedStockCards("123", new ArrayList<String>());
verify(mockedSharedPreferenceMgr).setStockLastSyncTime();
}
@Test
public void shouldNotSyncUpWhenHasSyncedUpLastDay() throws LMISException {
when(mockedSharedPreferenceMgr.hasSyncedUpLatestMovementLastDay()).thenReturn(true);
syncUpManager.syncUpUnSyncedStockCardCodes();
verify(mockedLmisRestApi, never()).syncUpUnSyncedStockCards("123", new ArrayList<String>());
verify(mockedSharedPreferenceMgr, never()).setLastMovementHandShakeDateToToday();
}
@Test
public void shouldSyncUpCmmsAndMarkThemAsSynced() throws Exception {
//given
List<Cmm> cmms = createCmmsData();
Cmm cmm = cmms.get(0);
when(mockedCmmRepository.listUnsynced()).thenReturn(cmms);
assertThat(cmm.isSynced(), is(false));
//when
syncUpManager.syncUpCmms();
//then
verify(mockedLmisRestApi, times(1)).syncUpCmms(eq("123"), anyListOf(CmmEntry.class));
assertThat(cmm.isSynced(), is(true));
verify(mockedCmmRepository).save(cmm);
}
@Test
public void shouldNotInvokeNetworkWhenNoUnsyncedCmmPresent() throws Exception {
//given
List<Cmm> emptyCmmsList = new ArrayList<>();
when(mockedCmmRepository.listUnsynced()).thenReturn(emptyCmmsList);
//when
syncUpManager.syncUpCmms();
//then
verify(mockedLmisRestApi, never()).syncUpCmms(anyString(), anyList());
}
@Test
public void shouldNotMarkCmmsAsSyncedWhenSyncUpFails() throws Exception {
//given sync up encounters network failure
List<Cmm> cmms = createCmmsData();
Cmm cmm = cmms.get(0);
when(mockedCmmRepository.listUnsynced()).thenReturn(cmms);
when(mockedLmisRestApi.syncUpCmms(any(String.class), anyList())).thenThrow(new LMISException("some error"));
assertThat(cmm.isSynced(), is(false));
//when
syncUpManager.syncUpCmms();
//then
verify(mockedLmisRestApi, times(1)).syncUpCmms(eq("123"), anyListOf(CmmEntry.class));
assertThat(cmm.isSynced(), is(false));
verify(mockedCmmRepository, never()).save(cmm);
}
private List<Cmm> createCmmsData() throws LMISException, ParseException {
Cmm cmm = new Cmm();
cmm.setStockCard(createTestStockCardData());
cmm.setPeriodBegin(new Date());
cmm.setPeriodEnd(new Date());
List<Cmm> cmms = new ArrayList<>();
cmms.add(cmm);
return cmms;
}
public class MyTestModule extends AbstractModule {
@Override
protected void configure() {
bind(RnrFormRepository.class).toInstance(mockedRnrFormRepository);
bind(SharedPreferenceMgr.class).toInstance(mockedSharedPreferenceMgr);
bind(SyncErrorsRepository.class).toInstance(mockedSyncErrorsRepository);
bind(CmmRepository.class).toInstance(mockedCmmRepository);
bind(ProgramDataFormRepository.class).toInstance(mockedProgramDataFormRepository);
}
}
}