PatientProgramValidatorTest.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.validator;

import java.util.Calendar;
import java.util.Date;
import java.util.Set;

import org.junit.Assert;
import org.junit.Test;
import org.openmrs.Patient;
import org.openmrs.PatientProgram;
import org.openmrs.PatientState;
import org.openmrs.ProgramWorkflow;
import org.openmrs.ProgramWorkflowState;
import org.openmrs.api.ProgramWorkflowService;
import org.openmrs.api.context.Context;
import org.openmrs.test.BaseContextSensitiveTest;
import org.openmrs.test.Verifies;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;

/**
 * Contains tests for the {@link PatientProgramValidator}
 */
public class PatientProgramValidatorTest extends BaseContextSensitiveTest {
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should fail if the patient field is blank", method = "validate(Object,Errors)")
	public void validate_shouldFailIfThePatientFieldIsBlank() throws Exception {
		PatientProgram program = new PatientProgram();
		BindException errors = new BindException(program, "program");
		new PatientProgramValidator().validate(program, errors);
		Assert.assertEquals(true, errors.hasFieldErrors("patient"));
	}
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 */
	@Test(expected = IllegalArgumentException.class)
	@Verifies(value = "should fail validation if obj is null", method = "validate(Object,Errors)")
	public void validate_shouldFailValidationIfObjIsNull() throws Exception {
		new PatientProgramValidator().validate(null, new BindException(new Object(), ""));
	}
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should fail if any patient state has an end date before its start date", method = "validate(Object,Errors)")
	public void validate_shouldFailIfAnyPatientStateHasAnEndDateBeforeItsStartDate() throws Exception {
		PatientProgram program = Context.getProgramWorkflowService().getPatientProgram(1);
		PatientState patientState = program.getStates().iterator().next();
		Calendar c = Calendar.getInstance();
		patientState.setStartDate(c.getTime());
		c.set(1970, 2, 1);//set to an old date
		patientState.setEndDate(c.getTime());
		
		BindException errors = new BindException(program, "");
		new PatientProgramValidator().validate(program, errors);
		Assert.assertTrue(errors.hasFieldErrors("states"));
	}
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should fail if there is more than one patientState with the same states and startDates", method = "validate(Object,Errors)")
	public void validate_shouldFailIfThereIsMoreThanOnePatientStateWithTheSameStatesAndStartDates() throws Exception {
		PatientProgram program = Context.getProgramWorkflowService().getPatientProgram(1);
		Set<PatientState> states = program.getStates();
		Assert.assertNotNull(states);
		PatientState patientState = states.iterator().next();
		PatientState duplicate = patientState.copy();
		states.add(duplicate);
		
		BindException errors = new BindException(program, "");
		new PatientProgramValidator().validate(program, errors);
		Assert.assertTrue(errors.hasFieldErrors("states"));
	}
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should fail if any patient state has a null work flow state", method = "validate(Object,Errors)")
	public void validate_shouldFailIfAnyPatientStateHasANullWorkFlowState() throws Exception {
		PatientProgram program = Context.getProgramWorkflowService().getPatientProgram(1);
		PatientState patientState = program.getStates().iterator().next();
		patientState.setState(null);
		
		BindException errors = new BindException(program, "");
		new PatientProgramValidator().validate(program, errors);
		Assert.assertTrue(errors.hasFieldErrors("states"));
	}
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should fail if the program property is null", method = "validate(Object,Errors)")
	public void validate_shouldFailIfTheProgramPropertyIsNull() throws Exception {
		PatientProgram program = new PatientProgram();
		program.setPatient(new Patient());
	}
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should fail if any patient states overlap each other in the same work flow", method = "validate(Object,Errors)")
	public void validate_shouldFailIfAnyPatientStatesOverlapEachOtherInTheSameWorkFlow() throws Exception {
		PatientProgram program = Context.getProgramWorkflowService().getPatientProgram(1);
		//Addition of new states to this program in the test data can make this test useless, so catch it her
		Assert.assertEquals(1, program.getStates().size());
		PatientState patientState1 = program.getStates().iterator().next();
		
		//Add a state that comes after patientState1
		PatientState patientState2 = new PatientState();
		patientState2.setStartDate(new Date());
		patientState2.setState(Context.getProgramWorkflowService().getWorkflowByUuid("84f0effa-dd73-46cb-b931-7cd6be6c5f81")
		        .getState(1));
		//guarantees that startDate of patientState2 is atleast 10ms earlier
		Thread.sleep(10);
		patientState1.setEndDate(new Date());
		
		program.getStates().add(patientState2);
		
		BindException errors = new BindException(program, "");
		new PatientProgramValidator().validate(program, errors);
		Assert.assertEquals(true, errors.hasFieldErrors("states"));
	}
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should pass for a valid program", method = "validate(Object,Errors)")
	public void validate_shouldPassForAValidProgram() throws Exception {
		PatientProgram program = Context.getProgramWorkflowService().getPatientProgram(1);
		PatientState patientState = program.getStates().iterator().next();
		patientState.getPatientProgram().transitionToState(patientState.getState().getProgramWorkflow().getState(4),
		    new Date());
		ValidateUtil.validate(program);
	}
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should fail if a patient program has duplicate states in the same work flow", method = "validate(Object,Errors)")
	public void validate_shouldFailIfAPatientProgramHasDuplicateStatesInTheSameWorkFlow() throws Exception {
		PatientProgram program = Context.getProgramWorkflowService().getPatientProgram(1);
		Set<PatientState> states = program.getStates();
		Assert.assertNotNull(states);
		PatientState patientState = states.iterator().next();
		PatientState duplicate = patientState.copy();
		states.add(duplicate);
		
		BindException errors = new BindException(program, "");
		new PatientProgramValidator().validate(program, errors);
		Assert.assertTrue(errors.hasFieldErrors("states"));
	}
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should fail if a patientState has an invalid work flow state", method = "validate(Object,Errors)")
	public void validate_shouldFailIfAPatientStateHasAnInvalidWorkFlowState() throws Exception {
		executeDataSet("org/openmrs/api/include/ProgramWorkflowServiceTest-otherProgramWorkflows.xml");
		PatientProgram program = Context.getProgramWorkflowService().getPatientProgram(1);
		PatientState patientState = program.getStates().iterator().next();
		patientState.setState(Context.getProgramWorkflowService().getStateByUuid("31c82d66-245c-11e1-9cf0-00248140a5eb"));
		
		BindException errors = new BindException(program, "");
		new PatientProgramValidator().validate(program, errors);
		Assert.assertTrue(errors.hasFieldErrors("states"));
	}
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should fail if a patient is in multiple states in the same work flow", method = "validate(Object,Errors)")
	public void validate_shouldFailIfAPatientIsInMultipleStatesInTheSameWorkFlow() throws Exception {
		PatientProgram program = Context.getProgramWorkflowService().getPatientProgram(1);
		PatientState patientState = program.getStates().iterator().next();
		patientState.getPatientProgram().transitionToState(patientState.getState().getProgramWorkflow().getState(4),
		    new Date());
		//make the closed state active
		patientState.setEndDate(null);
		BindException errors = new BindException(program, "");
		new PatientProgramValidator().validate(program, errors);
		Assert.assertTrue(errors.hasFieldErrors("states"));
	}
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should pass if a patient is in multiple states in different work flows", method = "validate(Object,Errors)")
	public void validate_shouldPassIfAPatientIsInMultipleStatesInDifferentWorkFlows() throws Exception {
		PatientProgram program = Context.getProgramWorkflowService().getPatientProgram(1);
		
		//Add another state to another work flow
		PatientState patientState2 = new PatientState();
		patientState2.setStartDate(new Date());
		patientState2.setState(Context.getProgramWorkflowService().getWorkflowByUuid("c66c8713-7df4-40de-96f6-dc4cce3432da")
		        .getState(5));
		program.getStates().add(patientState2);
		
		ValidateUtil.validate(program);
	}
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should pass if the start date of the first patient state in the work flow is null", method = "validate(Object,Errors)")
	public void validate_shouldPassIfTheStartDateOfTheFirstPatientStateInTheWorkFlowIsNull() throws Exception {
		PatientProgram program = Context.getProgramWorkflowService().getPatientProgram(1);
		Assert.assertEquals(1, program.getStates().size());//sanity check
		PatientState patientState = program.getStates().iterator().next();
		patientState.setStartDate(null);
		
		BindException errors = new BindException(program, "");
		new PatientProgramValidator().validate(program, errors);
		Assert.assertEquals(false, errors.hasFieldErrors("states"));
	}
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should pass for patient states that have the same start dates in the same work flow", method = "validate(Object,Errors)")
	public void validate_shouldPassForPatientStatesThatHaveTheSameStartDatesInTheSameWorkFlow() throws Exception {
		PatientProgram program = Context.getProgramWorkflowService().getPatientProgram(1);
		PatientState patientState = program.getStates().iterator().next();
		//add a new state by moving the patient to a another one
		ProgramWorkflowState nextState = patientState.getState().getProgramWorkflow().getState(4);
		patientState.getPatientProgram().transitionToState(nextState, patientState.getStartDate());
		
		BindException errors = new BindException(program, "");
		new PatientProgramValidator().validate(program, errors);
		Assert.assertEquals(false, errors.hasFieldErrors("states"));
	}
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should fail if there is more than one state with a null start date in the same workflow", method = "validate(Object,Errors)")
	public void validate_shouldFailIfThereIsMoreThanOneStateWithANullStartDateInTheSameWorkflow() throws Exception {
		ProgramWorkflowService pws = Context.getProgramWorkflowService();
		Patient patient = Context.getPatientService().getPatient(6);
		PatientProgram pp = new PatientProgram();
		pp.setPatient(patient);
		pp.setProgram(pws.getProgram(1));
		ProgramWorkflow testWorkflow = pp.getProgram().getWorkflow(1);
		
		//Add 2 other patient states with null start date		
		PatientState newPatientState1 = new PatientState();
		newPatientState1.setState(testWorkflow.getState(1));
		pp.getStates().add(newPatientState1);
		
		PatientState newPatientState2 = new PatientState();
		newPatientState2.setState(testWorkflow.getState(2));
		pp.getStates().add(newPatientState2);
		
		BindException errors = new BindException(pp, "");
		new PatientProgramValidator().validate(pp, errors);
		Assert.assertEquals(true, errors.hasFieldErrors("states"));
	}
	
	/**
	 * @see {@link PatientProgramValidator#validate(Object,Errors)}
	 * this test is to specifically validate fix for https://tickets.openmrs.org/browse/TRUNK-3670
	 */
	@Test
	@Verifies(value = "should not fail if a non-voided patient state is associated with a retired workflow", method = "validate(Object,Errors)")
	public void validate_shouldNotFailIfPatientStateIsInRetiredWorkflow() throws Exception {
		ProgramWorkflowService pws = Context.getProgramWorkflowService();
		Patient patient = Context.getPatientService().getPatient(6);
		
		// create a patient program
		PatientProgram pp = new PatientProgram();
		pp.setPatient(patient);
		pp.setProgram(pws.getProgram(1));
		
		// add a test workflow, and put the patient in a state in that workflow
		ProgramWorkflow testWorkflow = pp.getProgram().getWorkflow(1);
		PatientState newPatientState = new PatientState();
		newPatientState.setState(testWorkflow.getState(1));
		pp.getStates().add(newPatientState);
		
		// now retire the workflow
		testWorkflow.setRetired(true);
		testWorkflow.setRetiredBy(Context.getAuthenticatedUser());
		testWorkflow.setRetireReason("test");
		
		BindException errors = new BindException(pp, "");
		new PatientProgramValidator().validate(pp, errors);
		Assert.assertEquals(false, errors.hasFieldErrors("states"));
	}
	
}