ConceptValidatorTest.java

package org.openmrs.validator;

import java.util.Calendar;
import java.util.Locale;

import org.junit.Assert;
import org.junit.Test;
import org.openmrs.Concept;
import org.openmrs.ConceptMap;
import org.openmrs.ConceptName;
import org.openmrs.ConceptReferenceTerm;
import org.openmrs.api.ConceptNameType;
import org.openmrs.api.ConceptService;
import org.openmrs.api.DuplicateConceptNameException;
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;

/**
 * Tests methods on the {@link ConceptValidator} class.
 */
public class ConceptValidatorTest extends BaseContextSensitiveTest {
	
	/**
	 * @verifies {@link ConceptValidator#validate(Object,Errors)} test = should fail if there is a
	 *           duplicate unretired concept name in the locale
	 */
	@Test(expected = DuplicateConceptNameException.class)
	@Verifies(value = "should fail if there is a duplicate unretired concept name in the locale", method = "validate(Concept)")
	public void validate_shouldFailIfThereIsADuplicateUnretiredConceptNameInTheLocale() throws Exception {
		Context.setLocale(new Locale("en"));
		Concept concept = Context.getConceptService().getConcept(5497);
		String duplicateName = concept.getFullySpecifiedName(Context.getLocale()).getName();
		
		ConceptName newName = new ConceptName(duplicateName, Context.getLocale());
		newName.setDateCreated(Calendar.getInstance().getTime());
		newName.setCreator(Context.getAuthenticatedUser());
		concept.addName(newName);
		Errors errors = new BindException(concept, "concept");
		new ConceptValidator().validate(concept, errors);
	}
	
	@Test(expected = IllegalArgumentException.class)
	@Verifies(value = "should fail if the object parameter is null", method = "validate(Concept)")
	public void validate_shouldFailIfTheObjectParameterIsNull() throws Exception {
		Errors errors = new BindException(null, "concept");
		new ConceptValidator().validate(null, errors);
		Assert.assertTrue(errors.hasErrors());
	}
	
	@Test
	@Verifies(value = "should pass if the concept is being updated with no name change", method = "validate(Concept)")
	public void validate_shouldPassIfTheConceptIsBeingUpdatedWithNoNameChange() throws Exception {
		
		Concept conceptToUpdate = Context.getConceptService().getConcept(5497);
		conceptToUpdate.setCreator(Context.getAuthenticatedUser());
		Errors errors = new BindException(conceptToUpdate, "concept");
		new ConceptValidator().validate(conceptToUpdate, errors);
		Assert.assertFalse(errors.hasErrors());
		
	}
	
	/**
	 * @see {@link ConceptValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should fail if any name is an empty string", method = "validate(Object,Errors)")
	public void validate_shouldFailIfAnyNameIsAnEmptyString() throws Exception {
		Concept concept = new Concept();
		concept.addName(new ConceptName("name", Context.getLocale()));
		concept.addName(new ConceptName("", Context.getLocale()));
		Errors errors = new BindException(concept, "concept");
		new ConceptValidator().validate(concept, errors);
		Assert.assertEquals(true, errors.hasErrors());
	}
	
	/**
	 * @see {@link ConceptValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "fail if any name is a null value", method = "validate(Object,Errors)")
	public void validate_shouldFailIfAnyNameIsANullValue() throws Exception {
		Concept concept = new Concept();
		concept.addName(new ConceptName("name", Context.getLocale()));
		concept.addName(new ConceptName(null, Context.getLocale()));
		Errors errors = new BindException(concept, "concept");
		new ConceptValidator().validate(concept, errors);
		Assert.assertEquals(true, errors.hasErrors());
	}
	
	/**
	 * Tests for duplicate names for the same concept and not with names for other concepts
	 * 
	 * @see {@link ConceptValidator#validate(Object,Errors)}
	 */
	@Test(expected = DuplicateConceptNameException.class)
	@Verifies(value = "should fail if any names in the same locale for this concept are similar", method = "validate(Object,Errors)")
	public void validate_shouldFailIfAnyNamesInTheSameLocaleForThisConceptAreSimilar() throws Exception {
		Concept concept = new Concept();
		concept.addName(new ConceptName("same name", Context.getLocale()));
		concept.addName(new ConceptName("same name", Context.getLocale()));
		Errors errors = new BindException(concept, "concept");
		new ConceptValidator().validate(concept, errors);
	}
	
	/**
	 * Tests for uniqueness amongst all fully specified names in the same locale
	 * 
	 * @see {@link ConceptValidator#validate(Object,Errors)}
	 */
	@Test(expected = DuplicateConceptNameException.class)
	@Verifies(value = "should fail if there is a duplicate unretired fully specified name in the same locale", method = "validate(Object,Errors)")
	public void validate_shouldFailIfThereIsADuplicateUnretiredFullySpecifiedNameInTheSameLocale() throws Exception {
		Context.setLocale(new Locale("en"));
		Concept concept = Context.getConceptService().getConcept(5497);
		Assert.assertEquals(true, concept.getFullySpecifiedName(Context.getLocale()).isFullySpecifiedName());
		String duplicateName = concept.getFullySpecifiedName(Context.getLocale()).getName();
		
		Concept anotherConcept = Context.getConceptService().getConcept(5089);
		anotherConcept.getFullySpecifiedName(Context.getLocale()).setName(duplicateName);
		Errors errors = new BindException(anotherConcept, "concept");
		new ConceptValidator().validate(anotherConcept, errors);
	}
	
	/**
	 * Tests for uniqueness amongst all preferred names in the same locale
	 * 
	 * @see {@link ConceptValidator#validate(Object,Errors)}
	 */
	@Test(expected = DuplicateConceptNameException.class)
	@Verifies(value = "should fail if there is a duplicate unretired preferred name in the same locale", method = "validate(Object,Errors)")
	public void validate_shouldFailIfThereIsADuplicateUnretiredPreferredNameInTheSameLocale() throws Exception {
		Context.setLocale(new Locale("en"));
		Concept concept = Context.getConceptService().getConcept(5497);
		ConceptName preferredName = new ConceptName("preferred name", Context.getLocale());
		concept.setPreferredName(preferredName);
		Context.getConceptService().saveConcept(concept);
		Assert.assertEquals("preferred name", concept.getPreferredName(Context.getLocale()).getName());
		
		Concept anotherConcept = Context.getConceptService().getConcept(5089);
		anotherConcept.getFullySpecifiedName(Context.getLocale()).setName("preferred name");
		
		Errors errors = new BindException(anotherConcept, "concept");
		new ConceptValidator().validate(anotherConcept, errors);
	}
	
	/**
	 * @see {@link ConceptValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should fail if there is no name explicitly marked as fully specified", method = "validate(Object,Errors)")
	public void validate_shouldFailIfThereIsNoNameExplicitlyMarkedAsFullySpecified() throws Exception {
		Concept concept = Context.getConceptService().getConcept(5497);
		for (ConceptName name : concept.getNames())
			name.setConceptNameType(null);
		
		Errors errors = new BindException(concept, "concept");
		new ConceptValidator().validate(concept, errors);
		Assert.assertEquals(true, errors.hasErrors());
	}
	
	/**
	 * @see {@link ConceptValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should pass if the concept has atleast one fully specified name added to it", method = "validate(Object,Errors)")
	public void validate_shouldPassIfTheConceptHasAtleastOneFullySpecifiedNameAddedToIt() throws Exception {
		Concept concept = new Concept();
		concept.addName(new ConceptName("one name", Context.getLocale()));
		Errors errors = new BindException(concept, "concept");
		new ConceptValidator().validate(concept, errors);
		Assert.assertEquals(false, errors.hasErrors());
	}
	
	/**
	 * @see {@link ConceptValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should pass if the duplicate ConceptName is neither preferred nor fully Specified", method = "validate(Object,Errors)")
	public void validate_shouldPassIfTheDuplicateConceptNameIsNeitherPreferredNorFullySpecified() throws Exception {
		Context.setLocale(new Locale("en"));
		Concept concept = Context.getConceptService().getConcept(5497);
		//use a synonym as the duplicate name
		ConceptName duplicateName = concept.getSynonyms(Context.getLocale()).iterator().next();
		Assert.assertEquals(true, duplicateName.isSynonym());
		
		Concept anotherConcept = Context.getConceptService().getConcept(5089);
		anotherConcept.getFullySpecifiedName(Context.getLocale()).setName(duplicateName.getName());
		Errors errors = new BindException(anotherConcept, "concept");
		new ConceptValidator().validate(anotherConcept, errors);
		Assert.assertEquals(false, errors.hasErrors());
	}
	
	/**
	 * @see {@link ConceptValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should pass if the concept with a duplicate name is retired", method = "validate(Object,Errors)")
	public void validate_shouldPassIfTheConceptWithADuplicateNameIsRetired() throws Exception {
		Context.setLocale(new Locale("en"));
		Concept concept = Context.getConceptService().getConcept(5497);
		concept.setRetired(true);
		Context.getConceptService().saveConcept(concept);
		String duplicateName = concept.getFullySpecifiedName(Context.getLocale()).getName();
		
		Concept anotherConcept = Context.getConceptService().getConcept(5089);
		anotherConcept.getFullySpecifiedName(Context.getLocale()).setName(duplicateName);
		Errors errors = new BindException(anotherConcept, "concept");
		new ConceptValidator().validate(anotherConcept, errors);
		Assert.assertEquals(false, errors.hasErrors());
	}
	
	/**
	 * @see ConceptValidator#validate(Object,Errors)
	 * @verifies pass if the concept being validated is retired and has a duplicate name
	 */
	@Test
	public void validate_shouldPassIfTheConceptBeingValidatedIsRetiredAndHasADuplicateName() throws Exception {
		Context.setLocale(new Locale("en"));
		Concept concept = Context.getConceptService().getConcept(5497);
		Context.getConceptService().saveConcept(concept);
		String duplicateName = concept.getFullySpecifiedName(Context.getLocale()).getName();
		
		Concept anotherConcept = Context.getConceptService().getConcept(5089);
		anotherConcept.setRetired(true);
		anotherConcept.getFullySpecifiedName(Context.getLocale()).setName(duplicateName);
		Errors errors = new BindException(anotherConcept, "concept");
		new ConceptValidator().validate(anotherConcept, errors);
		Assert.assertEquals(false, errors.hasErrors());
	}
	
	/**
	 * @see {@link ConceptValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should pass if the concept has a synonym that is also a short name", method = "validate(Object,Errors)")
	public void validate_shouldPassIfTheConceptHasASynonymThatIsAlsoAShortName() throws Exception {
		Concept concept = new Concept();
		concept.addName(new ConceptName("CD4", Context.getLocale()));
		// Add the short name. Because the short name is not counted as a Synonym. 
		// ConceptValidator will not record any errors.
		ConceptName name = new ConceptName("CD4", Context.getLocale());
		name.setConceptNameType(ConceptNameType.SHORT);
		concept.addName(name);
		Errors errors = new BindException(concept, "concept");
		new ConceptValidator().validate(concept, errors);
		Assert.assertFalse(errors.hasErrors());
	}
	
	/**
	 * @see {@link ConceptValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should fail if a term is mapped multiple times to the same concept", method = "validate(Object,Errors)")
	public void validate_shouldFailIfATermIsMappedMultipleTimesToTheSameConcept() throws Exception {
		Concept concept = new Concept();
		ConceptService cs = Context.getConceptService();
		concept.addName(new ConceptName("my name", Context.getLocale()));
		ConceptMap map1 = new ConceptMap(cs.getConceptReferenceTerm(1), cs.getConceptMapType(1));
		concept.addConceptMapping(map1);
		ConceptMap map2 = new ConceptMap(cs.getConceptReferenceTerm(1), cs.getConceptMapType(1));
		concept.addConceptMapping(map2);
		
		Errors errors = new BindException(concept, "concept");
		new ConceptValidator().validate(concept, errors);
		
		//the second mapping should be rejected
		Assert.assertEquals(true, errors.hasFieldErrors("conceptMappings[1]"));
	}
	
	/**
	 * @see {@link ConceptValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should pass if the duplicate name in the locale for the concept being validated is voided", method = "validate(Object,Errors)")
	public void validate_shouldPassIfTheDuplicateNameInTheLocaleForTheConceptBeingValidatedIsVoided() throws Exception {
		ConceptService cs = Context.getConceptService();
		ConceptName otherName = cs.getConceptName(1439);
		//sanity check since names should only be unique amongst preferred and fully specified names
		Assert.assertTrue(otherName.isFullySpecifiedName() || otherName.isPreferred());
		Assert.assertFalse(otherName.isVoided());
		Assert.assertFalse(otherName.getConcept().isRetired());
		
		//change to a duplicate name in the same locale
		ConceptName duplicateName = cs.getConceptName(2477);
		duplicateName.setName(otherName.getName());
		Concept concept = duplicateName.getConcept();
		concept.setPreferredName(duplicateName);
		//ensure that the name has been marked as preferred in its locale
		Assert.assertEquals(duplicateName, concept.getPreferredName(duplicateName.getLocale()));
		Assert.assertTrue(duplicateName.isPreferred());
		duplicateName.setVoided(true);
		
		Errors errors = new BindException(concept, "concept");
		new ConceptValidator().validate(concept, errors);
		Assert.assertFalse(errors.hasErrors());
	}
	
	/**
	 * @see ConceptValidator#validate(Object,Errors)
	 * @verifies fail if there is a duplicate unretired concept name in the same locale different
	 *           than the system locale
	 */
	@Test(expected = DuplicateConceptNameException.class)
	public void validate_shouldFailIfThereIsADuplicateUnretiredConceptNameInTheSameLocaleDifferentThanTheSystemLocale()
	        throws Exception {
		Context.setLocale(new Locale("pl"));
		Locale en = new Locale("en");
		Concept concept = Context.getConceptService().getConcept(5497);
		Assert.assertEquals(true, concept.getFullySpecifiedName(en).isFullySpecifiedName());
		String duplicateName = concept.getFullySpecifiedName(en).getName();
		
		Concept anotherConcept = Context.getConceptService().getConcept(5089);
		anotherConcept.getFullySpecifiedName(en).setName(duplicateName);
		Errors errors = new BindException(anotherConcept, "concept");
		new ConceptValidator().validate(anotherConcept, errors);
	}
	
	/**
	 * @see {@link ConceptValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should pass for a new concept with a map created with deprecated concept map methods", method = "validate(Object,Errors)")
	public void validate_shouldPassForANewConceptWithAMapCreatedWithDeprecatedConceptMapMethods() throws Exception {
		ConceptService cs = Context.getConceptService();
		Concept concept = new Concept();
		concept.addName(new ConceptName("test name", Context.getLocale()));
		ConceptMap map = new ConceptMap();
		map.setSourceCode("unique code");
		map.setSource(cs.getConceptSource(1));
		concept.addConceptMapping(map);
		ValidateUtil.validate(concept);
	}
	
	/**
	 * @see {@link ConceptValidator#validate(Object,Errors)}
	 */
	@Test
	@Verifies(value = "should pass for an edited concept with a map created with deprecated concept map methods", method = "validate(Object,Errors)")
	public void validate_shouldPassForAnEditedConceptWithAMapCreatedWithDeprecatedConceptMapMethods() throws Exception {
		ConceptService cs = Context.getConceptService();
		Concept concept = cs.getConcept(5497);
		ConceptMap map = new ConceptMap();
		map.setSourceCode("unique code");
		map.setSource(cs.getConceptSource(1));
		concept.addConceptMapping(map);
		ValidateUtil.validate(concept);
	}
	
	/**
	 * @see ConceptValidator#validate(Object,Errors)
	 * @verifies not fail if a term has two new mappings on it
	 */
	@Test
	public void validate_shouldNotFailIfATermHasTwoNewMappingsOnIt() throws Exception {
		Concept concept = new Concept();
		ConceptService cs = Context.getConceptService();
		concept.addName(new ConceptName("my name", Context.getLocale()));
		ConceptReferenceTerm newTerm = new ConceptReferenceTerm(cs.getConceptSource(1), "1234", "term one two three four");
		ConceptMap map1 = new ConceptMap(newTerm, cs.getConceptMapType(1));
		concept.addConceptMapping(map1);
		ConceptReferenceTerm newTermTwo = new ConceptReferenceTerm(cs.getConceptSource(1), "12345",
		        "term one two three four five");
		ConceptMap map2 = new ConceptMap(newTermTwo, cs.getConceptMapType(1));
		concept.addConceptMapping(map2);
		
		Errors errors = new BindException(concept, "concept");
		new ConceptValidator().validate(concept, errors);
		
		//the second mapping should be rejected
		Assert.assertEquals(false, errors.hasFieldErrors("conceptMappings[1]"));
	}
}