/*
* This program is part of the OpenLMIS logistics management information system platform software.
* Copyright © 2013 VillageReach
*
* 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.stockmanagement.controller;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.mockito.Mock;
import org.openlmis.authentication.web.UserAuthenticationSuccessHandler;
import org.openlmis.core.builder.FacilityBuilder;
import org.openlmis.core.builder.ProductBuilder;
import org.openlmis.core.builder.ProgramBuilder;
import org.openlmis.core.builder.ProgramProductBuilder;
import org.openlmis.core.domain.*;
import org.openlmis.core.repository.FacilityRepository;
import org.openlmis.core.repository.StockAdjustmentReasonRepository;
import org.openlmis.core.service.*;
import org.openlmis.core.web.OpenLmisResponse;
import org.openlmis.db.categories.UnitTests;
import org.openlmis.stockmanagement.domain.*;
import org.openlmis.stockmanagement.dto.StockEvent;
import org.openlmis.stockmanagement.dto.StockEventType;
import org.openlmis.stockmanagement.repository.LotRepository;
import org.openlmis.stockmanagement.repository.StockCardRepository;
import org.openlmis.stockmanagement.service.StockCardService;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.modules.junit4.PowerMockRunnerDelegate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;
import java.util.*;
import static com.natpryce.makeiteasy.MakeItEasy.*;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
@Category(UnitTests.class)
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(BlockJUnit4ClassRunner.class)
public class StockCardControllerTest {
@Mock
private FacilityRepository facilityRepository;
@Mock
private ProductService productService;
@Mock
private MessageService messageService;
@Mock
private StockCardRepository stockCardRepository;
@Mock
private StockAdjustmentReasonRepository stockAdjustmentReasonRepository;
@Mock
private LotRepository lotRepository;
@Mock
private ProgramProductService programProductService;
@Mock
private ProgramService programService;
@Mock
private RoleRightsService roleRightsService;
@Mock
private StockCardService stockCardService;
private StockCardController controller;
private static final long USER_ID = 1L;
private static final Facility defaultFacility;
private static final Product defaultProduct;
private static final ProgramProduct defaultProgramProduct;
private static final Program defaultProgram;
private static final StockCard dummyCard;
private static final MockHttpServletRequest request = new MockHttpServletRequest();
private static final MockHttpSession session = new MockHttpSession();
private long fId;
private String pCode;
private String reasonName;
private StockAdjustmentReason reason;
private StockEvent event;
static {
defaultFacility = make(a(FacilityBuilder.defaultFacility, with(FacilityBuilder.facilityId, 1L)));
defaultProduct = make(a(ProductBuilder.defaultProduct, with(ProductBuilder.code, "valid_code")));
defaultProgramProduct = make(a(ProgramProductBuilder.defaultProgramProduct));
defaultProgram = make(a(ProgramBuilder.defaultProgram, with(ProgramBuilder.programId, 1L)));
dummyCard = StockCard.createZeroedStockCard(defaultFacility, defaultProduct);
}
@Before
public void setup() {
request.setSession(session);
session.setAttribute(UserAuthenticationSuccessHandler.USER_ID, USER_ID);
controller = new StockCardController(messageService,
facilityRepository,
productService,
stockAdjustmentReasonRepository,
stockCardRepository,
lotRepository,
programProductService,
programService,
roleRightsService,
stockCardService);
}
public void setupEvent() {
fId = defaultFacility.getId();
pCode = defaultProduct.getCode();
reasonName = "dummyReason";
reason = new StockAdjustmentReason();
reason.setAdditive(false);
reason.setName(reasonName);
event = new StockEvent();
event.setProductCode(pCode);
event.setType(StockEventType.ADJUSTMENT);
// event.setFacilityId(fId);
event.setReasonName(reasonName);
event.setQuantity(10L);
}
public Lot setupLot(Long id)
{
Lot lot = new Lot();
lot.setId(id);
lot.setProduct(defaultProduct);
lot.setLotCode("code_" + id);
lot.setManufacturerName("Manufacturer_of_" + id);
lot.setManufactureDate(new Date());
lot.setExpirationDate(new Date());
//event.setLot(lot);
return lot;
}
//Associate two lots with the specified StockCard - one lot for which we have stockOnHand, and one for which we don't
public void associateTestLotsWithStockCard(StockCard card)
{
LotOnHand lotWithZeroQuantityOnHand = LotOnHand.createZeroedLotOnHand(setupLot(0L), dummyCard);
LotOnHand lotWithPositiveQuantityOnHand = LotOnHand.createZeroedLotOnHand(setupLot(1L), dummyCard);
lotWithPositiveQuantityOnHand.setQuantityOnHand(1L);
List<LotOnHand> lotsOnHand = new LinkedList<>();
lotsOnHand.add(lotWithZeroQuantityOnHand);
lotsOnHand.add(lotWithPositiveQuantityOnHand);
card.setLotsOnHand(lotsOnHand);
}
public void setupGetStockCardCalls() {
when(stockCardRepository.getStockCardByFacilityAndProduct(any(Long.class), any(String.class))).thenReturn(dummyCard);
when(stockCardService.getStockCardById(any(Long.class), any(Long.class))).thenReturn(dummyCard);
when(stockCardService.getStockCards(any(Long.class))).thenReturn(new LinkedList<>(Collections.singletonList(dummyCard)));
}
public void setupPermissionCalls(List<Right> rights) {
when(stockCardRepository.getProductByStockCardId(any(Long.class))).thenReturn(defaultProduct);
when(facilityRepository.getById(any(Long.class))).thenReturn(defaultFacility);
when(roleRightsService.getRightsForUserFacilityAndProductCode(any(Long.class), any(Long.class), any(String.class))).thenReturn(rights);
}
@Test
public void shouldSucceedWithGettingStockCards() {
Long facilityId = 1L;
String productCode = "2";
Long stockCardId = 3L;
Integer numEntries = 1;
ResponseEntity response;
StockCard stockCard;
setupGetStockCardCalls();
setupPermissionCalls(Collections.singletonList(new Right("VIEW_STOCK_ON_HAND", RightType.REQUISITION)));
response = controller.getStockCard(facilityId, productCode, numEntries, true, request);
assertThat(response.getStatusCode(), is(HttpStatus.OK));
stockCard = (StockCard)response.getBody();
assertEquals(dummyCard, stockCard);
response = controller.getStockCardById(facilityId, stockCardId, numEntries, true, request);
assertThat(response.getStatusCode(), is(HttpStatus.OK));
stockCard = (StockCard)response.getBody();
assertEquals(dummyCard, stockCard);
response = controller.getStockCards(facilityId, numEntries, false, true, request);
assertThat(response.getStatusCode(), is(HttpStatus.OK));
OpenLmisResponse openLmisResponse = (OpenLmisResponse)response.getBody();
List<StockCard> stockCards = (List<StockCard>)openLmisResponse.getData().get("stockCards");
assertEquals(1, stockCards.size());
stockCard = stockCards.get(0);
assertEquals(dummyCard, stockCard);
}
@Test
public void shouldGetCountOnlyWhenSpecified() {
Long facilityId = 1L;
Integer numEntries = 100;
ResponseEntity response;
setupGetStockCardCalls();
setupPermissionCalls(Collections.singletonList(new Right("VIEW_STOCK_ON_HAND", RightType.REQUISITION)));
response = controller.getStockCards(facilityId, numEntries, true, true, request);
assertThat(response.getStatusCode(), is(HttpStatus.OK));
OpenLmisResponse openLmisResponse = (OpenLmisResponse)response.getBody();
int count = (int)openLmisResponse.getData().get("count");
assertEquals(1, count);
}
@Test
public void shouldErrorWithIncorrectPermissionsForGettingStockCards() {
Long facilityId = 1L;
String productCode = "2";
Long stockCardId = 3L;
Integer numEntries = 100;
ResponseEntity response;
setupGetStockCardCalls();
setupPermissionCalls(Collections.<Right>emptyList());
response = controller.getStockCard(facilityId, productCode, numEntries, true, request);
assertThat(response.getStatusCode(), is(HttpStatus.FORBIDDEN));
response = controller.getStockCardById(facilityId, stockCardId, numEntries, true, request);
assertThat(response.getStatusCode(), is(HttpStatus.FORBIDDEN));
// This one does not return an error, but returns an empty stock card list
response = controller.getStockCards(facilityId, numEntries, false, true, request);
assertThat(response.getStatusCode(), is(HttpStatus.OK));
OpenLmisResponse openLmisResponse = (OpenLmisResponse)response.getBody();
List<StockCard> stockCards = (List<StockCard>)openLmisResponse.getData().get("stockCards");
assertEquals(0, stockCards.size());
}
@Test
public void shouldSucceedWithEmptyStockEventList() {
List<StockEvent> events = Collections.emptyList();
long facilityId = 1;
ResponseEntity response = controller.processStock(facilityId, events, request);
assertThat(response.getStatusCode(), is(HttpStatus.OK));
}
@Test
public void shouldErrorWithInvalidStockEvent() {
List<StockEvent> events = Collections.singletonList(new StockEvent());
long facilityId = 1;
ResponseEntity response = controller.processStock(facilityId, events, request);
assertThat(response.getStatusCode(), is(HttpStatus.BAD_REQUEST));
}
@Test
public void shouldSucceedWithValidStockEvent() {
setupEvent();
// test
when(facilityRepository.getById(fId)).thenReturn(defaultFacility);
when(productService.getByCode(pCode)).thenReturn(defaultProduct);
when(stockAdjustmentReasonRepository.getAdjustmentReasonByName(reasonName)).thenReturn(reason);
when(stockCardService.getOrCreateStockCard(fId, pCode)).thenReturn(dummyCard);
when(lotRepository.getLotOnHandByStockCardAndLot(eq(dummyCard.getId()), any(Long.class))).thenReturn(null);
setupPermissionCalls(Collections.singletonList(new Right("MANAGE_STOCK", RightType.REQUISITION)));
ResponseEntity response = controller.processStock(fId, Collections.singletonList(event), request);
// verify
StockCardEntry entry = new StockCardEntry(dummyCard, StockCardEntryType.ADJUSTMENT, event.getQuantity() * -1, null, null);
entry.setAdjustmentReason(reason);
verify(stockCardService).addStockCardEntries(Collections.singletonList(entry));
assertThat(response.getStatusCode(), is(HttpStatus.OK));
}
@Test
public void shouldReturnNullWhenLotsOnHandIsNull()
{
//Arbitrary values
Long facilityId = 1L;
String productCode = "2";
Integer numEntries = 100;
when(stockCardRepository.getStockCardByFacilityAndProduct(any(Long.class), any(String.class))).thenReturn(dummyCard);
setupPermissionCalls(Collections.singletonList(new Right("VIEW_STOCK_ON_HAND", RightType.REQUISITION)));
boolean includeEmptyLots = false;
ResponseEntity response = controller.getStockCard(facilityId, productCode, numEntries, includeEmptyLots, request);
StockCard stockCard = (StockCard)response.getBody();
assertNull(stockCard.getLotsOnHand());
includeEmptyLots = true;
response = controller.getStockCard(facilityId, productCode, numEntries, includeEmptyLots, request);
stockCard = (StockCard)response.getBody();
assertNull(stockCard.getLotsOnHand());
}
@Test
public void shouldErrorWithIncorrectPermissionsForProcessingStock() {
setupEvent();
// test
when(facilityRepository.getById(fId)).thenReturn(defaultFacility);
when(productService.getByCode(pCode)).thenReturn(defaultProduct);
when(stockAdjustmentReasonRepository.getAdjustmentReasonByName(reasonName)).thenReturn(reason);
when(stockCardService.getOrCreateStockCard(fId, pCode)).thenReturn(dummyCard);
when(lotRepository.getLotOnHandByStockCardAndLot(eq(dummyCard.getId()), any(Long.class))).thenReturn(null);
setupPermissionCalls(Collections.<Right>emptyList());
ResponseEntity response = controller.processStock(fId, Collections.singletonList(event), request);
assertThat(response.getStatusCode(), is(HttpStatus.FORBIDDEN));
}
@Test
public void shouldOnlyReturnEmptyLotsWhenRequested()
{
//Arbitrary values
Long facilityId = 1L;
String productCode = "2";
Long stockCardId = 3L;
Integer numEntries = 100;
Boolean countOnly = false;
setupGetStockCardCalls();
setupPermissionCalls(Collections.singletonList(new Right("VIEW_STOCK_ON_HAND", RightType.REQUISITION)));
/* Indicate that empty Lots shouldn’t be included in stockCard.getLotsOnHand().
Then, below, verify that various method calls return just a single LotOnHand
(having stripped out the second, empty lot, that otherwise would have been included). */
boolean includeEmptyLots = false;
associateTestLotsWithStockCard(dummyCard);
ResponseEntity response = controller.getStockCard(facilityId, productCode, numEntries, includeEmptyLots, request);
StockCard stockCard = (StockCard)response.getBody();
assertEquals( 1, stockCard.getLotsOnHand().size());
associateTestLotsWithStockCard(dummyCard);
response = controller.getStockCardById(facilityId, stockCardId, numEntries, includeEmptyLots, request);
stockCard = (StockCard)response.getBody();
assertEquals( 1, stockCard.getLotsOnHand().size());
associateTestLotsWithStockCard(dummyCard);
response = controller.getStockCards(facilityId, numEntries, countOnly, includeEmptyLots, request);
OpenLmisResponse openLmisResponse = (OpenLmisResponse)response.getBody();
List<StockCard> stockCards = (List<StockCard>)openLmisResponse.getData().get("stockCards");
stockCard = stockCards.get(0);
assertEquals( 1, stockCard.getLotsOnHand().size());
/* Indicate that empty Lots should be included in stockCard.getLotsOnHand().
Then, below, verify that various method calls return both lots (including the empty one)
associated with our stockCard. */
includeEmptyLots = true;
associateTestLotsWithStockCard(dummyCard);
response = controller.getStockCard(facilityId, productCode, numEntries, includeEmptyLots, request);
stockCard = (StockCard)response.getBody();
assertEquals( 2, stockCard.getLotsOnHand().size());
associateTestLotsWithStockCard(dummyCard);
response = controller.getStockCardById(facilityId, stockCardId, numEntries, includeEmptyLots, request);
stockCard = (StockCard)response.getBody();
assertEquals( 2, stockCard.getLotsOnHand().size());
associateTestLotsWithStockCard(dummyCard);
response = controller.getStockCards(facilityId, numEntries, countOnly, includeEmptyLots, request);
openLmisResponse = (OpenLmisResponse)response.getBody();
stockCards = (List<StockCard>)openLmisResponse.getData().get("stockCards");
stockCard = stockCards.get(0);
assertEquals( 2, stockCard.getLotsOnHand().size());
}
}