OrderServiceTest.java

/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.api;

import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.openmrs.Concept;
import org.openmrs.Drug;
import org.openmrs.DrugOrder;
import org.openmrs.Encounter;
import org.openmrs.GenericDrug;
import org.openmrs.Order;
import org.openmrs.Order.OrderAction;
import org.openmrs.Orderable;
import org.openmrs.Patient;
import org.openmrs.User;
import org.openmrs.api.context.Context;
import org.openmrs.test.BaseContextSensitiveTest;
import org.openmrs.test.Verifies;
import org.openmrs.util.OpenmrsUtil;

import static java.util.Collections.synchronizedSet;

/**
 * TODO clean up and test all methods in OrderService
 */
public class OrderServiceTest extends BaseContextSensitiveTest {
	
	private static final String simpleOrderEntryDatasetFilename = "org/openmrs/api/include/OrderServiceTest-simpleOrderEntryTestDataset.xml";
	
	protected static final String DRUG_ORDERS_DATASET_XML = "org/openmrs/api/include/OrderServiceTest-drugOrdersList.xml";
	
	protected static final String ORDERS_DATASET_XML = "org/openmrs/api/include/OrderServiceTest-ordersList.xml";
	
	private OrderService service;
	
	@Before
	public void before() {
		this.service = Context.getOrderService();
	}
	
	/**
	 * @see {@link OrderService#saveOrder(Order)}
	 */
	@Test(expected = APIException.class)
	@Verifies(value = "should not save order if order doesnt validate", method = "saveOrder(Order)")
	public void saveOrder_shouldNotSaveOrderIfOrderDoesntValidate() throws Exception {
		OrderService orderService = Context.getOrderService();
		Order order = new Order();
		order.setPatient(null);
		orderService.saveOrder(order);
	}
	
	/**
	 * @see {@link OrderService#saveOrder(Order)}
	 */
	@Test
	@Verifies(value = "should save new version of an existing order", method = "saveOrder(Order)")
	public void saveOrder_shouldSaveNewVersionOfAnExistingOrder() throws Exception {
		OrderService orderService = Context.getOrderService();
		Order order = orderService.getOrder(3);
		Order newOrder = orderService.saveOrder(order);
		Assert.assertTrue(order.getOrderId() != newOrder.getOrderId());
	}
	
	/**
	 * @see {@link OrderService#getOrderByUuid(String)}
	 */
	@Test
	@Verifies(value = "should find object given valid uuid", method = "getOrderByUuid(String)")
	public void getOrderByUuid_shouldFindObjectGivenValidUuid() throws Exception {
		String uuid = "921de0a3-05c4-444a-be03-e01b4c4b9142";
		Order order = Context.getOrderService().getOrderByUuid(uuid);
		Assert.assertEquals(1, (int) order.getOrderId());
	}
	
	/**
	 * @see {@link OrderService#getOrderByUuid(String)}
	 */
	@Test
	@Verifies(value = "should return null if no object found with given uuid", method = "getOrderByUuid(String)")
	public void getOrderByUuid_shouldReturnNullIfNoObjectFoundWithGivenUuid() throws Exception {
		Assert.assertNull(Context.getOrderService().getOrderByUuid("some invalid uuid"));
	}
	
	/**
	 * @see {@link OrderService#getOrderByOrderNumber(String)}
	 */
	@Test
	@Verifies(value = "should find object given valid order number", method = "getOrderByOrderNumber(String)")
	public void getOrderByOrderNumber_shouldFindObjectGivenValidOrderNumber() throws Exception {
		Order order = Context.getOrderService().getOrderByOrderNumber("1");
		Assert.assertNotNull(order);
		Assert.assertEquals(1, (int) order.getOrderId());
	}
	
	/**
	 * @see {@link OrderService#getOrderByOrderNumber(String)}
	 */
	@Test
	@Verifies(value = "should return null if no object found with given order number", method = "getOrderByOrderNumber(String)")
	public void getOrderByOrderNumber_shouldReturnNullIfNoObjectFoundWithGivenOrderNumber() throws Exception {
		Assert.assertNull(Context.getOrderService().getOrderByOrderNumber("some invalid order number"));
	}
	
	/**
	 * @see {@link OrderService#getOrder(Integer)}
	 */
	@Test
	@Verifies(value = "should find object given valid order id", method = "getOrder(Integer)")
	public void getOrder_shouldFindObjectGivenValidOrderId() throws Exception {
		Order order = Context.getOrderService().getOrder(1);
		Assert.assertNotNull(order);
	}
	
	/**
	 * @see {@link OrderService#getOrder(Integer)}
	 */
	@Test
	@Verifies(value = "should return null if no object found with given order id", method = "getOrder(Integer)")
	public void getOrder_shouldReturnNullIfNoObjectFoundWithGivenOrderId() throws Exception {
		Assert.assertNull(Context.getOrderService().getOrder(999));
	}
	
	/**
	 * @see {@link OrderService#purgeOrder(Order)}
	 */
	@Test
	@Verifies(value = "should delete order", method = "purgeOrder(Order)")
	public void purgeOrder_shouldDeleteOrder() throws Exception {
		Order order = Context.getOrderService().getOrder(1);
		Assert.assertNotNull(order);
		Context.getOrderService().purgeOrder(order);
		order = Context.getOrderService().getOrder(1);
		Assert.assertNull(order);
	}
	
	/**
	 * @see {@link OrderService#getOrderHistoryByConcept(Patient,Concept)}
	 */
	@Test
	@Verifies(value = "should return orders with the given concept", method = "getOrderHistoryByConcept(Patient,Concept)")
	public void getOrderHistoryByConcept_shouldReturnOrdersWithTheGivenConcept() throws Exception {
		//We should have two orders with this concept.
		Concept concept = Context.getConceptService().getConcept(88);
		Patient patient = Context.getPatientService().getPatient(2);
		List<Order> orders = Context.getOrderService().getOrderHistoryByConcept(patient, concept);
		Assert.assertEquals(2, orders.size());
		for (Order order : orders)
			Assert.assertTrue(order.getOrderId() == 4 || order.getOrderId() == 5);
		
		//We should two different orders with this concept
		concept = Context.getConceptService().getConcept(792);
		orders = Context.getOrderService().getOrderHistoryByConcept(patient, concept);
		Assert.assertEquals(2, orders.size());
		for (Order order : orders)
			Assert.assertTrue(order.getOrderId() == 2 || order.getOrderId() == 3);
	}
	
	/**
	 * @see {@link OrderService#getOrderHistoryByConcept(Patient, Concept)}
	 */
	@Test
	@Verifies(value = "should return empty list for concept without orders", method = "getOrderHistoryByConcept(Patient,Concept)")
	public void getOrderHistoryByConcept_shouldReturnEmptyListForConceptWithoutOrders() throws Exception {
		Concept concept = Context.getConceptService().getConcept(21);
		Patient patient = Context.getPatientService().getPatient(2);
		List<Order> orders = Context.getOrderService().getOrderHistoryByConcept(patient, concept);
		Assert.assertEquals(0, orders.size());
	}
	
	/**
	 * @see {@link OrderService#getOrderables(String)}
	 */
	@Test
	@Verifies(value = "should get orderable concepts by name and drug class", method = "getOrderables(String)")
	public void getOrderables_shouldGetOrderableConceptsByNameAndDrugClass() throws Exception {
		executeDataSet(simpleOrderEntryDatasetFilename);
		
		String query = "Ampi";
		
		List<Orderable<?>> result = Context.getOrderService().getOrderables(query);
		
		Assert.assertNotNull(result);
		
		Assert.assertEquals(3, result.size());
		
		Boolean isExpected = result.get(0).getClass().equals(GenericDrug.class);
		Assert.assertTrue(isExpected);
		isExpected = result.get(1).getClass().equals(GenericDrug.class);
		Assert.assertTrue(isExpected);
		isExpected = result.get(2).getClass().equals(Drug.class);
		Assert.assertTrue(isExpected);
	}
	
	/**
	 * @see {@link OrderService#getOrderables(String)}
	 */
	@Test(expected = IllegalArgumentException.class)
	@Verifies(value = "should fail if null passed in", method = "getOrderables(String)")
	public void getOrderables_shouldFailIfNullPassedIn() throws Exception {
		executeDataSet(simpleOrderEntryDatasetFilename);
		
		String query = null;
		Context.getOrderService().getOrderables(query);
	}
	
	/**
	 * @see {@link OrderService#getOrderable(String)}
	 */
	@Test(expected = IllegalArgumentException.class)
	@Verifies(value = "should fail if null passed in", method = "getOrderable(String)")
	public void getOrderable_shouldFailIfNullPassedIn() throws Exception {
		Context.getOrderService().getOrderable(null);
	}
	
	/**
	 * @see {@link OrderService#getOrderable(String)}
	 */
	@Test
	@Verifies(value = "should fetch an orderable with given identifier", method = "getOrderable(String)")
	public void getOrderable_shouldFetchAnOrderableWithGivenIdentifier() throws Exception {
		Orderable orderable = Context.getOrderService().getOrderable("org.openmrs.GenericDrug:concept=3");
		Assert.assertNotNull(orderable);
		Assert.assertTrue(orderable.getClass().equals(GenericDrug.class));
		
		orderable = Context.getOrderService().getOrderable("org.openmrs.Drug:2");
		Assert.assertNotNull(orderable);
		Assert.assertTrue(orderable.getClass().equals(Drug.class));
	}
	
	/**
	 * @see OrderService#saveOrder(Order)
	 * @verifies not allow you to change the order number of a saved order
	 */
	@Test
	public void saveOrder_shouldNotAllowYouToChangeTheOrderNumberOfASavedOrder() throws Exception {
		Order existing = service.getOrder(1);
		existing.setOrderNumber("New Number");
		try {
			service.saveOrder(existing);
			Assert.fail("the previous line should have thrown an exception");
		}
		catch (APIException ex) {
			// test this way rather than @Test(expected...) so we can verify it's the right APIException
			Assert.assertTrue(ex.getMessage().contains("orderNumber"));
		}
	}
	
	/**
	 * @see {@link OrderService#discontinueOrder(Order,String,User,Date)}
	 */
	@Test
	@Verifies(value = "should discontinue and return the old order", method = "discontinueOrder(Order,String,User,Date)")
	public void discontinueOrder_shouldDiscontinueAndReturnTheOldOrder() throws Exception {
		int originalCount = service.getOrders(Order.class, null, null, null, null, null, null, null).size();
		Order order = service.getOrder(3);
		
		Assert.assertFalse(order.getDiscontinued());
		Assert.assertNull(order.getDiscontinuedDate());
		Assert.assertNull(order.getDiscontinuedBy());
		Assert.assertNull(order.getDiscontinuedReason());
		Order returnedOrder = service.discontinueOrder(order, "Testing");
		Assert.assertEquals(order, returnedOrder);
		Assert.assertTrue(order.getDiscontinued());
		Assert.assertEquals("Testing", returnedOrder.getDiscontinuedReason());
		Assert.assertNotNull(returnedOrder.getDiscontinuedDate());
		Assert.assertNotNull(returnedOrder.getDiscontinuedBy());
		//should have created a discontinue order
		Assert.assertEquals(originalCount + 1, service.getOrders(Order.class, null, null, null, null, null, null, null)
		        .size());
		//find the newly created order and make ensure that its action is DISCONTINUE
		Order discontinueOrder = null;
		for (Order o : service.getOrders(Order.class, null, null, null, null, null, null, null)) {
			if (OpenmrsUtil.nullSafeEquals(o.getPreviousOrderNumber(), order.getOrderNumber()))
				discontinueOrder = o;
		}
		
		Assert.assertNotNull(discontinueOrder);
		Assert.assertEquals(OrderAction.DISCONTINUE, discontinueOrder.getOrderAction());
	}
	
	/**
	 * @see {@link OrderService#discontinueOrder(Order,String,User,Date)}
	 */
	@Test(expected = APIException.class)
	@Verifies(value = "should fail if the discontinue date is after the auto expire date", method = "discontinueOrder(Order,String,User,Date)")
	public void discontinueOrder_shouldFailIfTheDiscontinueDateIsAfterTheAutoExpireDate() throws Exception {
		Order order = service.getOrder(12);
		Assert.assertNotNull(order.getAutoExpireDate());
		Calendar cal = Calendar.getInstance();
		//set the time to after auto expire date
		cal.setTime(order.getAutoExpireDate());
		cal.add(Calendar.MINUTE, 1);
		service.discontinueOrder(order, "Testing", null, cal.getTime());
	}
	
	/**
	 * @see {@link OrderService#discontinueOrder(Order,String,User,Date)}
	 */
	@Test(expected = APIException.class)
	@Verifies(value = "should fail if the order is already discontinued", method = "discontinueOrder(Order,String,User,Date)")
	public void discontinueOrder_shouldFailIfTheOrderIsAlreadyDiscontinued() throws Exception {
		Order order = service.getOrder(3);
		Assert.assertFalse(order.getDiscontinued());
		service.discontinueOrder(order, "Testing");
		Assert.assertTrue(order.getDiscontinued());
		//re discontinue
		service.discontinueOrder(order, "Testing2");
	}
	
	/**
	 * @see {@link OrderService#discontinueOrder(Order,String,User,Date)}
	 */
	@Test(expected = APIException.class)
	@Verifies(value = "should fail if the passed in discontinue date is in the future", method = "discontinueOrder(Order,String,User,Date)")
	public void discontinueOrder_shouldFailIfThePassedInDiscontinueDateIsInTheFuture() throws Exception {
		Order order = service.getOrder(3);
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.MINUTE, 1);
		service.discontinueOrder(order, "Testing", null, cal.getTime());
	}
	
	/**
	 * @see OrderService#getNewOrderNumber()
	 * @verifies always return unique orderNumbers when called multiple times without saving orders
	 */
	@Test
	public void getNewOrderNumber_shouldAlwaysReturnUniqueOrderNumbersWhenCalledMultipleTimesWithoutSavingOrders()
	        throws Exception {
		final int numberOfConcurrentOrderNumberRequests = 16;
		final Set<String> uniqueOrderNumbers = synchronizedSet(new HashSet<String>());
		final CountDownLatch beginRequestingOrderNumbers = new CountDownLatch(1);
		final CountDownLatch newOrderNumbersObtained = new CountDownLatch(numberOfConcurrentOrderNumberRequests);
		Runnable orderNumberRequest = new Runnable() {
			
			@Override
			public void run() {
				try {
					beginRequestingOrderNumbers.await();
					Context.openSession();
					String orderNumber = service.getNewOrderNumber();
					uniqueOrderNumbers.add(orderNumber);
				}
				catch (InterruptedException e) {}
				finally {
					Context.closeSession();
					newOrderNumbersObtained.countDown();
				}
			}
		};
		for (int i = 0; i < numberOfConcurrentOrderNumberRequests; i++) {
			new Thread(orderNumberRequest).start();
		}
		
		beginRequestingOrderNumbers.countDown();
		newOrderNumbersObtained.await();
		
		Assert.assertEquals("Should receive a unique order number for each concurrent request",
		    numberOfConcurrentOrderNumberRequests, uniqueOrderNumbers.size());
	}
	
	/**
	 * @see OrderService#getDrugOrdersByPatientAndIngredient(Patient,Concept)
	 * @verifies return drug orders matched by patient and intermediate concept
	 */
	@Test
	public void getDrugOrdersByPatientAndIngredient_shouldReturnDrugOrdersMatchedByPatientAndIntermediateConcept()
	        throws Exception {
		OrderService orderService = Context.getOrderService();
		List<DrugOrder> drugOrders = orderService.getDrugOrdersByPatientAndIngredient(new Patient(2), new Concept(88));
		Assert.assertEquals(4, drugOrders.size());
	}
	
	/**
	 * @see OrderService#getDrugOrdersByPatientAndIngredient(Patient,Concept)
	 * @verifies return drug orders matched by patient and drug concept
	 */
	@Test
	public void getDrugOrdersByPatientAndIngredient_shouldReturnDrugOrdersMatchedByPatientAndDrugConcept() throws Exception {
		OrderService orderService = Context.getOrderService();
		List<DrugOrder> drugOrders = orderService.getDrugOrdersByPatientAndIngredient(new Patient(2), new Concept(792));
		Assert.assertEquals(2, drugOrders.size());
	}
	
	/**
	 * @see OrderService#getDrugOrdersByPatientAndIngredient(Patient,Concept)
	 * @verifies return empty list if no concept matched
	 */
	@Test
	public void getDrugOrdersByPatientAndIngredient_shouldReturnEmptyListIfNoConceptMatched() throws Exception {
		OrderService orderService = Context.getOrderService();
		List<DrugOrder> drugOrders = orderService.getDrugOrdersByPatientAndIngredient(new Patient(2), new Concept(80));
		Assert.assertEquals(0, drugOrders.size());
	}
	
	/**
	 * @see OrderService#getDrugOrdersByPatientAndIngredient(Patient,Concept)
	 * @verifies return empty list if no patient matched
	 */
	@Test
	public void getDrugOrdersByPatientAndIngredient_shouldReturnEmptyListIfNoPatientMatched() throws Exception {
		OrderService orderService = Context.getOrderService();
		List<DrugOrder> drugOrders = orderService.getDrugOrdersByPatientAndIngredient(new Patient(10), new Concept(88));
		Assert.assertEquals(0, drugOrders.size());
	}
	
	/**
	 * @see {@link OrderService#getOrdersByPatient(Patient, boolean)}
	 */
	@Test
	@Verifies(value = "return list of orders for patient with respect to the include voided flag", method = "getOrdersByPatient(Patient, boolean)")
	public void getOrdersByPatient_shouldReturnListOfOrdersForPatientWithRespectToTheIncludeVoidedFlag() throws Exception {
		executeDataSet(ORDERS_DATASET_XML);
		Patient p = Context.getPatientService().getPatient(2);
		List<Order> orders = Context.getOrderService().getOrdersByPatient(p, true);
		Assert.assertEquals(12, orders.size());
		
		orders = Context.getOrderService().getOrdersByPatient(p, false);
		Assert.assertEquals(8, orders.size());
	}
	
	/**
	 * @see {@link OrderService#getOrdersByPatient(Patient)}
	 */
	@Test
	@Verifies(value = "return list of non voided orders for patient", method = "getOrdersByPatient(Patient)")
	public void getOrdersByPatient_shouldReturnListOfNonVoidedOrdersForPatient() throws Exception {
		Patient p = Context.getPatientService().getPatient(2);
		List<Order> orders = Context.getOrderService().getOrdersByPatient(p);
		Assert.assertEquals(8, orders.size());
	}
	
	/**
	 * @see OrderService#voidOrder(Order,String)
	 */
	@Test(expected = IllegalArgumentException.class)
	@Verifies(value = "should fail if reason is empty", method = "voidOrder(Order,String)")
	public void voidOrder_shouldFailIfReasonIsEmpty() throws Exception {
		OrderService orderService = Context.getOrderService();
		
		Order order = orderService.getOrder(2);
		Assert.assertNotNull(order);
		
		String voidReason = "";
		orderService.voidOrder(order, "");
	}
	
	/**
	 * @see {@link OrderService#voidOrder(Order,String)}
	 */
	@Test(expected = IllegalArgumentException.class)
	@Verifies(value = "should fail if reason is null", method = "voidOrder(Order,String)")
	public void voidOrder_shouldFailIfReasonIsNull() throws Exception {
		OrderService orderService = Context.getOrderService();
		
		Order order = orderService.getOrder(2);
		Assert.assertNotNull(order);
		
		String voidReason = null;
		orderService.voidOrder(order, voidReason);
	}
	
	/**
	 * @see {@link OrderService#voidOrder(Order,String)}
	 */
	@Test
	@Verifies(value = "should void given order", method = "voidOrder(Order,String)")
	public void voidOrder_shouldVoidGivenOrder() throws Exception {
		OrderService orderService = Context.getOrderService();
		
		Order order = orderService.getOrder(2);
		Assert.assertNotNull(order);
		
		String voidReason = "test reason";
		orderService.voidOrder(order, voidReason);
		
		// assert that order is voided and void reason is set
		Assert.assertTrue(order.isVoided());
		Assert.assertEquals(voidReason, order.getVoidReason());
	}
	
	/**
	 * @see {@link OrderService#voidOrder(Order,String)}
	 */
	@Test
	@Verifies(value = "should not change an already voided order", method = "voidOrder(Order,String)")
	public void voidOrder_shouldNotChangeAnAlreadyVoidedOrder() throws Exception {
		executeDataSet(DRUG_ORDERS_DATASET_XML);
		OrderService orderService = Context.getOrderService();
		
		Order order = orderService.getOrder(8);
		Assert.assertNotNull(order);
		// assert that order has been already voided
		Assert.assertTrue(order.isVoided());
		String expectedVoidReason = order.getVoidReason();
		
		String voidReason = "test reason";
		orderService.voidOrder(order, voidReason);
		
		// assert that voiding does not make an affect
		Assert.assertTrue(order.isVoided());
		Assert.assertEquals(expectedVoidReason, order.getVoidReason());
	}
	
	/**
	 * @see {@link OrderService#unvoidOrder(Order)}
	 */
	@Test
	@Verifies(value = "should unvoid given order", method = "voidOrder(Order)")
	public void unvoidOrder_shouldUnvoidGivenOrder() throws Exception {
		executeDataSet(DRUG_ORDERS_DATASET_XML);
		OrderService orderService = Context.getOrderService();
		
		Order order = orderService.getOrder(8);
		Assert.assertNotNull(order);
		// assert that order has been already voided
		Assert.assertTrue(order.isVoided());
		
		orderService.unvoidOrder(order);
		
		Assert.assertFalse(order.isVoided());
	}
	
	/**
	 * @see {@link OrderService#getOrdersByEncounter(Encounter)}
	 */
	@Test
	@Verifies(value = "return list of non voided orders by encounter", method = "getOrdersByEncounter(Encounter)")
	public void getOrdersByEncounter_shouldReturnListOfNonVoidedOrdersByEncounter() throws Exception {
		executeDataSet(ORDERS_DATASET_XML);
		Encounter encounter = new Encounter(3);
		List<Order> orders = Context.getOrderService().getOrdersByEncounter(encounter);
		Assert.assertEquals(3, orders.size());
	}
	
	/**
	 * @see {@link OrderService#getOrdersByOrderer(User)}
	 */
	@Test
	@Verifies(value = "return list of non voided orders by orderer", method = "getOrdersByOrderer(User)")
	public void getOrdersByOrderer_shouldReturnListOfNonVoidedOrdersByOrderer() throws Exception {
		User user = Context.getUserService().getUser(1);
		List<Order> orders = Context.getOrderService().getOrdersByOrderer(user);
		Assert.assertEquals(10, orders.size());
	}
	
	/**
	 * @see {@link OrderService#saveOrder(Order)}
	 */
	@Test
	@Verifies(value = "should asign order number for new order", method = "saveOrder(Order)")
	public void saveOrder_shouldAssignOrderNumberForNewOrder() throws Exception {
		Order order = new Order();
		order.setConcept(Context.getConceptService().getConcept(23));
		order.setPatient(Context.getPatientService().getPatient(6));
		order.setStartDate(new Date());
		service.saveOrder(order);
		Assert.assertNotNull(order.getOrderId());
		Assert.assertNotNull(order.getOrderNumber());
	}
}