package org.openlmis.core.service;
import android.content.Context;
import android.content.SharedPreferences;
import com.google.inject.AbstractModule;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openlmis.core.LMISTestApp;
import org.openlmis.core.LMISTestRunner;
import org.openlmis.core.exceptions.LMISException;
import org.openlmis.core.manager.SharedPreferenceMgr;
import org.openlmis.core.manager.UserInfoMgr;
import org.openlmis.core.model.Product;
import org.openlmis.core.model.ProductProgram;
import org.openlmis.core.model.Program;
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.User;
import org.openlmis.core.model.builder.ProductBuilder;
import org.openlmis.core.model.builder.ProductProgramBuilder;
import org.openlmis.core.model.builder.ProgramDataFormBuilder;
import org.openlmis.core.model.builder.StockCardBuilder;
import org.openlmis.core.model.builder.StockMovementItemBuilder;
import org.openlmis.core.model.repository.ProductRepository;
import org.openlmis.core.model.repository.ProgramRepository;
import org.openlmis.core.model.repository.RnrFormRepository;
import org.openlmis.core.model.repository.StockRepository;
import org.openlmis.core.model.service.StockService;
import org.openlmis.core.network.LMISRestApi;
import org.openlmis.core.network.model.ProductAndSupportedPrograms;
import org.openlmis.core.network.model.SyncDownLatestProductsResponse;
import org.openlmis.core.network.model.SyncDownProgramDataResponse;
import org.openlmis.core.network.model.SyncDownRequisitionsResponse;
import org.openlmis.core.network.model.SyncDownStockCardResponse;
import org.openlmis.core.service.SyncDownManager.SyncProgress;
import org.openlmis.core.utils.DateUtil;
import org.robolectric.RuntimeEnvironment;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import roboguice.RoboGuice;
import rx.Scheduler;
import rx.android.plugins.RxAndroidPlugins;
import rx.android.plugins.RxAndroidSchedulersHook;
import rx.observers.TestSubscriber;
import rx.schedulers.Schedulers;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyList;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.openlmis.core.service.SyncDownManager.SyncProgress.ProductSynced;
import static org.openlmis.core.service.SyncDownManager.SyncProgress.RapidTestsSynced;
import static org.openlmis.core.service.SyncDownManager.SyncProgress.RequisitionSynced;
import static org.openlmis.core.service.SyncDownManager.SyncProgress.StockCardsLastMonthSynced;
import static org.openlmis.core.service.SyncDownManager.SyncProgress.StockCardsLastYearSynced;
import static org.openlmis.core.service.SyncDownManager.SyncProgress.SyncingProduct;
import static org.openlmis.core.service.SyncDownManager.SyncProgress.SyncingRapidTests;
import static org.openlmis.core.service.SyncDownManager.SyncProgress.SyncingRequisition;
import static org.openlmis.core.service.SyncDownManager.SyncProgress.SyncingStockCardsLastMonth;
import static org.openlmis.core.service.SyncDownManager.SyncProgress.SyncingStockCardsLastYear;
import static org.roboguice.shaded.goole.common.collect.Lists.newArrayList;
@RunWith(LMISTestRunner.class)
public class SyncDownManagerTest {
private SyncDownManager syncDownManager;
private LMISRestApi lmisRestApi;
private SharedPreferenceMgr sharedPreferenceMgr;
private StockMovementItem stockMovementItem;
private ProgramRepository programRepository;
private RnrFormRepository rnrFormRepository;
private StockRepository stockRepository;
private SharedPreferences createdPreferences;
private ProductRepository productRepository;
private Product productWithKits;
private StockService stockService;
@Before
public void setUp() throws Exception {
sharedPreferenceMgr = mock(SharedPreferenceMgr.class);
lmisRestApi = mock(LMISRestApi.class);
rnrFormRepository = mock(RnrFormRepository.class);
programRepository = mock(ProgramRepository.class);
productRepository = mock(ProductRepository.class);
stockRepository = mock(StockRepository.class);
stockService = mock(StockService.class);
reset(rnrFormRepository);
reset(lmisRestApi);
RoboGuice.overrideApplicationInjector(RuntimeEnvironment.application, new MyTestModule());
syncDownManager = RoboGuice.getInjector(RuntimeEnvironment.application).getInstance(SyncDownManager.class);
syncDownManager.lmisRestApi = lmisRestApi;
User user = new User();
user.setFacilityCode("HF XXX");
user.setFacilityId("123");
UserInfoMgr.getInstance().setUser(user);
RxAndroidPlugins.getInstance().reset();
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
@Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
});
syncDownManager.stockService = stockService;
}
@Test
public void shouldSyncDownServerData() throws Exception {
//given
mockSyncDownLatestProductResponse();
mockRequisitionResponse();
mockStockCardsResponse();
mockRapidTestsResponse();
//when
SyncServerDataSubscriber subscriber = new SyncServerDataSubscriber();
syncDownManager.syncDownServerData(subscriber);
subscriber.awaitTerminalEvent();
subscriber.assertNoErrors();
//then
assertThat(subscriber.syncProgresses.get(0), is(SyncingProduct));
assertThat(subscriber.syncProgresses.get(1), is(ProductSynced));
assertThat(subscriber.syncProgresses.get(2), is(SyncingStockCardsLastMonth));
assertThat(subscriber.syncProgresses.get(3), is(StockCardsLastMonthSynced));
assertThat(subscriber.syncProgresses.get(4), is(SyncingRequisition));
assertThat(subscriber.syncProgresses.get(5), is(RequisitionSynced));
assertThat(subscriber.syncProgresses.get(6), is(SyncingRapidTests));
assertThat(subscriber.syncProgresses.get(7), is(RapidTestsSynced));
assertThat(subscriber.syncProgresses.get(8), is(SyncingStockCardsLastYear));
assertThat(subscriber.syncProgresses.get(9), is(StockCardsLastYearSynced));
verify(stockService).immediatelyUpdateAvgMonthlyConsumption();
}
@Test
public void shouldOnlySyncOnceWhenInvokedTwice() throws Exception {
//given
mockSyncDownLatestProductResponse();
mockRequisitionResponse();
mockStockCardsResponse();
mockRapidTestsResponse();
//when
CountOnNextSubscriber firstEnterSubscriber = new CountOnNextSubscriber();
CountOnNextSubscriber laterEnterSubscriber = new CountOnNextSubscriber();
syncDownManager.syncDownServerData(firstEnterSubscriber);
syncDownManager.syncDownServerData(laterEnterSubscriber);
firstEnterSubscriber.awaitTerminalEvent();
firstEnterSubscriber.assertNoErrors();
laterEnterSubscriber.assertNoTerminalEvent();
//then
assertThat(firstEnterSubscriber.syncProgresses.size(), is(10));
assertThat(laterEnterSubscriber.syncProgresses.size(), is(0));
}
@Test
public void shouldSyncDownNewLatestProductList() throws Exception {
mockSyncDownLatestProductResponse();
mockRequisitionResponse();
mockStockCardsResponse();
mockRapidTestsResponse();
when(productRepository.getByCode(anyString())).thenReturn(new Product());
Program program = new Program();
when(programRepository.queryByCode("PR")).thenReturn(program);
SyncServerDataSubscriber subscriber = new SyncServerDataSubscriber();
syncDownManager.syncDownServerData(subscriber);
subscriber.awaitTerminalEvent();
subscriber.assertNoErrors();
verify(lmisRestApi).fetchLatestProducts(anyString());
verify(productRepository).batchCreateOrUpdateProducts(anyList());
verify(sharedPreferenceMgr).setLastSyncProductTime("today");
}
@Test
public void shouldUpdateNotifyBannerListWhenSOHIsZeroAndProductIsDeActive() throws Exception {
//given
Product product = new Product();
product.setPrimaryName("name");
product.setActive(false);
product.setCode("code");
product.setArchived(false);
Product existingProduct = ProductBuilder.create().setCode("code").setIsActive(true).setIsArchived(true).build();
when(productRepository.getByCode(product.getCode())).thenReturn(existingProduct);
StockCard stockCard = new StockCard();
stockCard.setProduct(product);
stockCard.setStockOnHand(0);
when(stockRepository.queryStockCardByProductId(anyLong())).thenReturn(stockCard);
//when
syncDownManager.updateDeactivateProductNotifyList(product);
//then
verify(sharedPreferenceMgr).setIsNeedShowProductsUpdateBanner(true, "name");
}
@Test
public void shouldNotUpdateNotifyBannerListWhenProductIsArchived() throws Exception {
//given
Product product = new Product();
product.setPrimaryName("name");
product.setActive(false);
product.setCode("code");
product.setArchived(true);
Product existingProduct = ProductBuilder.create().setCode("code").setIsActive(true).setIsArchived(true).build();
when(productRepository.getByCode(product.getCode())).thenReturn(existingProduct);
StockCard stockCard = new StockCard();
stockCard.setProduct(product);
stockCard.setStockOnHand(0);
when(stockRepository.queryStockCardByProductId(anyLong())).thenReturn(stockCard);
//when
syncDownManager.updateDeactivateProductNotifyList(product);
//then
verify(sharedPreferenceMgr, never()).setIsNeedShowProductsUpdateBanner(true, "name");
}
@Test
public void shouldRemoveNotifyBannerListWhenReactiveProduct() throws Exception {
//given
Product product = new Product();
product.setPrimaryName("new name");
product.setActive(true);
product.setCode("code");
Product existingProduct = ProductBuilder.create().setCode("code").setIsActive(false).setPrimaryName("name").build();
when(productRepository.getByCode(product.getCode())).thenReturn(existingProduct);
StockCard stockCard = new StockCard();
stockCard.setStockOnHand(0);
when(stockRepository.queryStockCardByProductId(anyLong())).thenReturn(stockCard);
//when
syncDownManager.updateDeactivateProductNotifyList(product);
//then
verify(sharedPreferenceMgr).removeShowUpdateBannerTextWhenReactiveProduct("name");
}
@Test
public void shouldNotUpdateBannerWhenExistingProductIsNull() throws Exception {
Product product = new Product();
when(productRepository.getByCode(anyString())).thenReturn(null);
syncDownManager.updateDeactivateProductNotifyList(product);
verify(stockRepository, times(0)).queryStockCardByProductId(anyLong());
}
private void testSyncProgress(SyncProgress progress) {
try {
if (progress == StockCardsLastMonthSynced) {
verifyLastMonthStockCardsSynced();
verify(sharedPreferenceMgr).setLastMonthStockCardDataSynced(true);
}
if (progress == RequisitionSynced) {
verify(rnrFormRepository, times(1)).createRnRsWithItems(any(ArrayList.class));
verify(sharedPreferenceMgr).setRequisitionDataSynced(true);
}
if (progress == StockCardsLastYearSynced) {
verify(lmisRestApi, times(13)).fetchStockMovementData(anyString(), anyString(), anyString());
verify(sharedPreferenceMgr).setShouldSyncLastYearStockCardData(false);
}
} catch (LMISException e) {
e.printStackTrace();
}
}
private void mockSyncDownLatestProductResponse() throws LMISException {
List<ProductAndSupportedPrograms> productsAndSupportedPrograms = new ArrayList<>();
ProductAndSupportedPrograms productAndSupportedPrograms = new ProductAndSupportedPrograms();
productWithKits = new Product();
productWithKits.setCode("ABC");
productAndSupportedPrograms.setProduct(productWithKits);
ProductProgram productProgram = new ProductProgramBuilder().setProductCode("ABC").setProgramCode("PR").setActive(true).build();
productAndSupportedPrograms.setProductPrograms(newArrayList(productProgram));
productsAndSupportedPrograms.add(productAndSupportedPrograms);
SyncDownLatestProductsResponse response = new SyncDownLatestProductsResponse();
response.setLatestUpdatedTime("today");
response.setLatestProducts(productsAndSupportedPrograms);
when(lmisRestApi.fetchLatestProducts(any(String.class))).thenReturn(response);
}
private void mockRequisitionResponse() throws LMISException {
when(sharedPreferenceMgr.getPreference()).thenReturn(LMISTestApp.getContext().getSharedPreferences("LMISPreference", Context.MODE_PRIVATE));
List<RnRForm> data = new ArrayList<>();
data.add(new RnRForm());
data.add(new RnRForm());
SyncDownRequisitionsResponse syncDownRequisitionsResponse = new SyncDownRequisitionsResponse();
syncDownRequisitionsResponse.setRequisitions(data);
when(lmisRestApi.fetchRequisitions(anyString())).thenReturn(syncDownRequisitionsResponse);
}
private void mockStockCardsResponse() throws ParseException, LMISException {
createdPreferences = LMISTestApp.getContext().getSharedPreferences("LMISPreference", Context.MODE_PRIVATE);
when(sharedPreferenceMgr.shouldSyncLastYearStockData()).thenReturn(true);
when(sharedPreferenceMgr.getPreference()).thenReturn(createdPreferences);
when(lmisRestApi.fetchStockMovementData(anyString(), anyString(), anyString())).thenReturn(getStockCardResponse());
when(stockRepository.list()).thenReturn(newArrayList(new StockCardBuilder().build()));
}
private void mockRapidTestsResponse() throws ParseException, LMISException {
createdPreferences = LMISTestApp.getContext().getSharedPreferences("LMISPreference", Context.MODE_PRIVATE);
when(sharedPreferenceMgr.isRapidTestDataSynced()).thenReturn(false);
when(sharedPreferenceMgr.getPreference()).thenReturn(createdPreferences);
when(lmisRestApi.fetchProgramDataForms(anyLong())).thenReturn(getRapidTestsResponse());
}
private SyncDownProgramDataResponse getRapidTestsResponse() {
ProgramDataForm programDataForm1 = new ProgramDataFormBuilder()
.setProgram(new Program())
.setPeriod(DateUtil.parseString("2016-03-21", DateUtil.DB_DATE_FORMAT))
.setStatus(ProgramDataForm.STATUS.AUTHORIZED)
.setSynced(false).build();
ProgramDataForm programDataForm2 = new ProgramDataFormBuilder()
.setProgram(new Program())
.setPeriod(DateUtil.parseString("2016-04-21", DateUtil.DB_DATE_FORMAT))
.setStatus(ProgramDataForm.STATUS.AUTHORIZED)
.setSynced(false).build();
SyncDownProgramDataResponse syncDownProgramDataResponse = new SyncDownProgramDataResponse();
syncDownProgramDataResponse.setProgramDataForms(newArrayList(programDataForm1, programDataForm2));
return syncDownProgramDataResponse;
}
private void verifyLastMonthStockCardsSynced() throws LMISException {
verify(lmisRestApi).fetchStockMovementData(anyString(), anyString(), anyString());
verify(sharedPreferenceMgr).setIsNeedsInventory(false);
verify(stockRepository).batchCreateSyncDownStockCardsAndMovements(any(List.class));
}
private SyncDownStockCardResponse getStockCardResponse() throws ParseException {
StockCard stockCard1 = StockCardBuilder.buildStockCard();
StockCard stockCard2 = StockCardBuilder.buildStockCard();
StockMovementItemBuilder builder = new StockMovementItemBuilder();
stockMovementItem = builder.build();
stockMovementItem.setSynced(false);
ArrayList<StockMovementItem> stockMovementItems1 = newArrayList(stockMovementItem, stockMovementItem, stockMovementItem);
stockCard1.setStockMovementItemsWrapper(stockMovementItems1);
ArrayList<StockMovementItem> stockMovementItems2 = newArrayList(stockMovementItem, stockMovementItem);
stockCard2.setStockMovementItemsWrapper(stockMovementItems2);
SyncDownStockCardResponse syncDownStockCardResponse = new SyncDownStockCardResponse();
syncDownStockCardResponse.setStockCards(newArrayList(stockCard1, stockCard2));
return syncDownStockCardResponse;
}
private class SyncServerDataSubscriber extends CountOnNextSubscriber {
@Override
public void onNext(SyncProgress syncProgress) {
super.onNext(syncProgress);
testSyncProgress(syncProgress);
}
}
private class CountOnNextSubscriber extends TestSubscriber<SyncProgress> {
public List<SyncProgress> syncProgresses = new ArrayList<>();
@Override
public void onNext(SyncProgress syncProgress) {
syncProgresses.add(syncProgress);
}
}
public class MyTestModule extends AbstractModule {
@Override
protected void configure() {
bind(RnrFormRepository.class).toInstance(rnrFormRepository);
bind(SharedPreferenceMgr.class).toInstance(sharedPreferenceMgr);
bind(ProgramRepository.class).toInstance(programRepository);
bind(ProductRepository.class).toInstance(productRepository);
bind(StockRepository.class).toInstance(stockRepository);
}
}
}