/* * Copyright 2012-2013 inBloom, Inc. and its affiliates. * * Licensed under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.slc.sli.ingestion.handler; import com.google.common.collect.ImmutableMap; import junit.framework.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Matchers; import org.mockito.Mockito; import org.slc.sli.dal.repository.MongoEntityRepository; import org.slc.sli.domain.*; import org.slc.sli.ingestion.ActionVerb; import org.slc.sli.ingestion.NeutralRecordEntity; import org.slc.sli.ingestion.reporting.AbstractMessageReport; import org.slc.sli.ingestion.reporting.ReportStats; import org.slc.sli.ingestion.reporting.Source; import org.slc.sli.ingestion.reporting.impl.CoreMessageCode; import org.slc.sli.ingestion.reporting.impl.DummyMessageReport; import org.slc.sli.ingestion.reporting.impl.SimpleReportStats; import org.slc.sli.ingestion.transformation.SimpleEntity; import org.slc.sli.validation.EntityValidationException; import org.slc.sli.validation.ValidationError; import org.slc.sli.validation.ValidationError.ErrorType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; /** * Tests for EntityPersistHandler * * @author dduran * */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "/spring/applicationContext-test.xml" }) public class EntityPersistHandlerTest { @Autowired private EntityPersistHandler entityPersistHandler; private MongoEntityRepository mockedEntityRepository; private static final String STUDENT_ID = "765432"; private static final String STAGED_STUDENT_UUID = "2012js-df7cbeb6-f11f-11e1-8f42-406c8f3fb40c"; private static final String STAGED_STUDENT_SCHOOL_UUID = "2012ab-df7cbeb6-f11f-11e1-8f42-406c8f3fb40c"; private static final String STAGED_TEACHER_SCHOOL_UUID = "2012cd-df7cbeb6-f11f-11e1-8f42-406c8f3fb40c"; private static final String SCHOOL_ID = "654321"; private static final String INTERNAL_STUDENT_ID = "0x" + STUDENT_ID; private static final String INTERNAL_SCHOOL_ID = "0x" + SCHOOL_ID; private static final String REGION_ID = "SLI"; private static final String METADATA_BLOCK = "metaData"; private static final String REGION_ID_FIELD = "tenantId"; private static final String EXTERNAL_ID_FIELD = "externalId"; private final LinkedList<Entity> studentList = new LinkedList<Entity>(); private final Iterable<Entity> studentFound = studentList; private final LinkedList<Entity> schoolList = new LinkedList<Entity>(); private final Iterable<Entity> schoolFound = schoolList; private final LinkedList<Entity> studentSchoolAssociationList = new LinkedList<Entity>(); private final Iterable<Entity> studentSchoolAssociationFound = studentSchoolAssociationList; private NeutralQuery regionIdStudentIdQuery = null; private NeutralQuery ssaQuery = null; @Value("${sli.ingestion.totalRetries}") private int totalRetries; @Before public void setup() { mockedEntityRepository = mock(MongoEntityRepository.class); entityPersistHandler.setEntityRepository(mockedEntityRepository); when(mockedEntityRepository.findAll(eq("student"), any(NeutralQuery.class))).thenReturn(studentFound); // School search. regionIdStudentIdQuery = new NeutralQuery(); regionIdStudentIdQuery.addCriteria(new NeutralCriteria(METADATA_BLOCK + "." + REGION_ID_FIELD, NeutralCriteria.OPERATOR_EQUAL, REGION_ID, false)); regionIdStudentIdQuery.addCriteria(new NeutralCriteria(METADATA_BLOCK + "." + EXTERNAL_ID_FIELD, NeutralCriteria.OPERATOR_EQUAL, STUDENT_ID, false)); when(mockedEntityRepository.findAll(eq("school"), eq(regionIdStudentIdQuery))).thenReturn(schoolFound); // Student-School Association search. ssaQuery = new NeutralQuery(); ssaQuery.addCriteria(new NeutralCriteria(METADATA_BLOCK + "." + REGION_ID_FIELD, NeutralCriteria.OPERATOR_EQUAL, REGION_ID, false)); ssaQuery.addCriteria(new NeutralCriteria("body.studentId", NeutralCriteria.OPERATOR_EQUAL, INTERNAL_STUDENT_ID, false)); ssaQuery.addCriteria(new NeutralCriteria("body.schoolId", NeutralCriteria.OPERATOR_EQUAL, INTERNAL_SCHOOL_ID, false)); when(mockedEntityRepository.findAll(eq("studentSchoolAssociation"), eq(ssaQuery))).thenReturn( studentSchoolAssociationFound); } /** * @author tshewchuk 2/6/2010 (PI3 US811) * @author tke 3/15/2012, modified be consistent with the new IdNormalization strategy. * Added testing of record DB lookup and update, and support for association entities. */ @Test public void testCreateStudentEntity() { MongoEntityRepository entityRepository = mock(MongoEntityRepository.class); // Student search. NeutralQuery query = new NeutralQuery(); query.addCriteria(new NeutralCriteria(METADATA_BLOCK + "." + REGION_ID_FIELD, NeutralCriteria.OPERATOR_EQUAL, REGION_ID, false)); query.addCriteria(new NeutralCriteria(METADATA_BLOCK + "." + EXTERNAL_ID_FIELD, NeutralCriteria.OPERATOR_EQUAL, STUDENT_ID, false)); // Create a new student entity with entity ID, and test creating it in the data store. SimpleEntity studentEntity = createStudentEntity(true); List<Entity> le = new ArrayList<Entity>(); le.add(studentEntity); when(entityRepository.findAll(eq("student"), any(NeutralQuery.class))).thenReturn(le); when(entityRepository.updateWithRetries(studentEntity.getType(), studentEntity, totalRetries)).thenReturn(true); entityPersistHandler.setEntityRepository(entityRepository); AbstractMessageReport errorReport = new DummyMessageReport(); ReportStats reportStats = new SimpleReportStats(); entityPersistHandler.handle(studentEntity, errorReport, reportStats); verify(entityRepository).updateWithRetries(studentEntity.getType(), studentEntity, totalRetries); // Test student entity without entity ID, so that repository will create a new one le.clear(); SimpleEntity studentEntity2 = createStudentEntity(false); le.add(studentEntity2); entityPersistHandler.handle(studentEntity2, errorReport, reportStats); verify(entityRepository).createWithRetries(studentEntity.getType(), null, studentEntity.getBody(), studentEntity.getMetaData(), "student", totalRetries); Assert.assertFalse("Error report should not contain errors", reportStats.hasErrors()); } @Test public void testCreateAndDeleteStudentEntity() { MongoEntityRepository entityRepository = mock(MongoEntityRepository.class); // Student search. NeutralQuery query = new NeutralQuery(); query.addCriteria(new NeutralCriteria(METADATA_BLOCK + "." + REGION_ID_FIELD, NeutralCriteria.OPERATOR_EQUAL, REGION_ID, false)); query.addCriteria(new NeutralCriteria(METADATA_BLOCK + "." + EXTERNAL_ID_FIELD, NeutralCriteria.OPERATOR_EQUAL, STUDENT_ID, false)); // Create a new student entity with entity ID, and test creating it in the data store. SimpleEntity studentEntity = createStudentEntity(true); List<Entity> le = new ArrayList<Entity>(); le.add(studentEntity); when(entityRepository.findAll(eq("student"), any(NeutralQuery.class))).thenReturn(le); when(entityRepository.updateWithRetries(studentEntity.getType(), studentEntity, totalRetries)).thenReturn(true); // Mock the return from safeDelete CascadeResult mockCascadeResult = new CascadeResult(); mockCascadeResult.setStatus(CascadeResult.Status.SUCCESS); when(entityRepository.safeDelete(eq(studentEntity), eq(studentEntity.getEntityId()), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyInt(), any(AccessibilityCheck.class))).thenReturn(mockCascadeResult); entityPersistHandler.setEntityRepository(entityRepository); AbstractMessageReport errorReport = new DummyMessageReport(); ReportStats reportStats = new SimpleReportStats(); entityPersistHandler.handle(studentEntity, errorReport, reportStats); verify(entityRepository).updateWithRetries(studentEntity.getType(), studentEntity, totalRetries); studentEntity.setAction( ActionVerb.CASCADE_DELETE); studentEntity.setActionAttributes(ImmutableMap.of( "Force", "true", "LogViolations", "true" )); entityPersistHandler.handle( studentEntity, errorReport, reportStats); verify(entityRepository).safeDelete(studentEntity, studentEntity.getEntityId(), true, false, true, true, null, null); Assert.assertFalse("Error report should not contain errors", reportStats.hasErrors()); } /** * @author tshewchuk 2/6/2010 (PI3 US811) * Added testing of record DB lookup and update, and support for association entities. */ @Test public void testUpdateStudentEntity() { MongoEntityRepository entityRepository = mock(MongoEntityRepository.class); AbstractMessageReport errorReport = new DummyMessageReport(); ReportStats reportStats = new SimpleReportStats(); SimpleEntity studentEntity = createStudentEntity(true); SimpleEntity existingStudentEntity = createStudentEntity(true); existingStudentEntity.setEntityId(UUID.randomUUID().toString()); // Student search. when(entityRepository.findAll(eq("student"), eq(regionIdStudentIdQuery))).thenReturn( Arrays.asList((Entity) existingStudentEntity)); when(entityRepository.updateWithRetries("student", studentEntity, totalRetries)).thenReturn(true); entityPersistHandler.setEntityRepository(entityRepository); studentEntity.getMetaData().put(EntityMetadataKey.TENANT_ID.getKey(), REGION_ID); entityPersistHandler.doHandling(studentEntity, errorReport, reportStats); verify(entityRepository).updateWithRetries("student", studentEntity, totalRetries); Assert.assertFalse("Error report should not contain errors", reportStats.hasErrors()); } @Test public void testPersistanceExceptionHandling() { MongoEntityRepository entityRepository = mock(MongoEntityRepository.class); DummyMessageReport errorReport = mock(DummyMessageReport.class); Mockito.doCallRealMethod().when(errorReport) .error(Mockito.any(ReportStats.class), Matchers.any(Source.class), Mockito.any(CoreMessageCode.class), Mockito.anyString() , Mockito.anyString(), Mockito.anyString(), Mockito.anyObject(), Mockito.any()); Mockito.doCallRealMethod().when(errorReport) .error(Mockito.any(Throwable.class), Mockito.any(ReportStats.class), Matchers.any(Source.class), Mockito.any(CoreMessageCode.class), Mockito.anyString() , Mockito.anyString(), Mockito.anyString(), Mockito.anyObject(), Mockito.any()); ReportStats reportStats = new SimpleReportStats(); SimpleEntity studentEntity = createStudentEntity(true); SimpleEntity existingStudentEntity = createStudentEntity(true); existingStudentEntity.setEntityId(UUID.randomUUID().toString()); // Student search. when(entityRepository.findAll(eq("student"), eq(regionIdStudentIdQuery))).thenReturn( Arrays.asList((Entity) existingStudentEntity)); ValidationError error = new ValidationError(ErrorType.REQUIRED_FIELD_MISSING, "field", null, new String[] { "String" }); when(entityRepository.updateWithRetries("student", studentEntity, totalRetries)).thenThrow( new EntityValidationException(existingStudentEntity.getEntityId(), "student", Arrays.asList(error))); entityPersistHandler.setEntityRepository(entityRepository); studentEntity.getMetaData().put(EntityMetadataKey.TENANT_ID.getKey(), REGION_ID); entityPersistHandler.doHandling(studentEntity, errorReport, reportStats); Assert.assertTrue("Error report should contain errors", reportStats.hasErrors()); Mockito.verify(errorReport, Mockito.times(1)).error(Matchers.any(ReportStats.class), Matchers.any(Source.class), Matchers.eq(CoreMessageCode.CORE_0006), Matchers.anyString(), Matchers.anyString(), Matchers.anyString(), Matchers.anyObject(), Matchers.any()); } /** * @author tshewchuk 2/6/2010 (PI3 US811) * Added testing of record DB lookup and update, and support for association entities. */ @Test public void testCreateStudentSchoolAssociationEntity() { MongoEntityRepository entityRepository = mock(MongoEntityRepository.class); AbstractMessageReport errorReport = new DummyMessageReport(); ReportStats reportStats = new SimpleReportStats(); // Create a new student-school association entity, and test creating it in the data store. SimpleEntity foundStudent = new SimpleEntity(); foundStudent.setEntityId(INTERNAL_STUDENT_ID); LinkedList<Entity> studentList = new LinkedList<Entity>(); studentList.add(foundStudent); // Student search. when(entityRepository.findAll(eq("student"), eq(regionIdStudentIdQuery))).thenReturn(studentList); // School search. SimpleEntity foundSchool = new SimpleEntity(); foundSchool.setEntityId(INTERNAL_SCHOOL_ID); LinkedList<Entity> schoolList = new LinkedList<Entity>(); schoolList.add(foundSchool); when(entityRepository.findAll(eq("school"), any(NeutralQuery.class))).thenReturn(schoolList); SimpleEntity studentSchoolAssociationEntity = createStudentSchoolAssociationEntity(STUDENT_ID, false); entityPersistHandler.setEntityRepository(entityRepository); studentSchoolAssociationEntity.getMetaData().put(EntityMetadataKey.TENANT_ID.getKey(), REGION_ID); entityPersistHandler.doHandling(studentSchoolAssociationEntity, errorReport, reportStats); verify(entityRepository).createWithRetries(studentSchoolAssociationEntity.getType(), null, studentSchoolAssociationEntity.getBody(), studentSchoolAssociationEntity.getMetaData(), studentSchoolAssociationEntity.getType(), totalRetries); Assert.assertFalse("Error report should not contain errors", reportStats.hasErrors()); } /** * @author tshewchuk 2/6/2010 (PI3 US811) * Added testing of record DB lookup and update, and support for association entities. */ @Test public void testUpdateStudentSchoolAssociationEntity() { MongoEntityRepository entityRepository = mock(MongoEntityRepository.class); AbstractMessageReport errorReport = new DummyMessageReport(); ReportStats reportStats = new SimpleReportStats(); // Create a new student-school association entity, and test creating it in the data store. NeutralRecordEntity foundStudent = new NeutralRecordEntity(null); LinkedList<Entity> studentList = new LinkedList<Entity>(); studentList.add(foundStudent); // Student search. when(entityRepository.findAll(eq("student"), eq(regionIdStudentIdQuery))).thenReturn(studentList); // School search. NeutralRecordEntity foundSchool = new NeutralRecordEntity(null); LinkedList<Entity> schoolList = new LinkedList<Entity>(); schoolList.add(foundSchool); when(entityRepository.findAll(eq("school"), eq(regionIdStudentIdQuery))).thenReturn(schoolList); SimpleEntity studentSchoolAssociationEntity = createStudentSchoolAssociationEntity(STUDENT_ID, true); SimpleEntity existingStudentSchoolAssociationEntity = createStudentSchoolAssociationEntity(STUDENT_ID, true); existingStudentSchoolAssociationEntity.setEntityId(UUID.randomUUID().toString()); when(entityRepository.findAll(eq("studentSchoolAssociation"), eq(ssaQuery))).thenReturn( Arrays.asList((Entity) existingStudentSchoolAssociationEntity)); when( entityRepository.updateWithRetries("studentSchoolAssociation", studentSchoolAssociationEntity, totalRetries)).thenReturn(true); entityPersistHandler.setEntityRepository(entityRepository); studentSchoolAssociationEntity.getMetaData().put(EntityMetadataKey.TENANT_ID.getKey(), REGION_ID); entityPersistHandler.doHandling(studentSchoolAssociationEntity, errorReport, reportStats); verify(entityRepository).updateWithRetries("studentSchoolAssociation", studentSchoolAssociationEntity, totalRetries); Assert.assertFalse("Error report should not contain errors", reportStats.hasErrors()); } @Test public void testHandleFailedValidation() { /* * when validation fails for an entity, we should not try to persist */ AbstractMessageReport report = new DummyMessageReport(); ReportStats reportStats = new SimpleReportStats(); SimpleEntity mockedEntity = mock(SimpleEntity.class); String expectedType = "student"; when(mockedEntity.getType()).thenReturn(expectedType); Map<String, Object> expectedMap = new HashMap<String, Object>(); when(mockedEntity.getBody()).thenReturn(expectedMap); Map<String, Object> expectedMetaData = new HashMap<String, Object>(); expectedMetaData.put(REGION_ID_FIELD, REGION_ID); expectedMetaData.put(EXTERNAL_ID_FIELD, STUDENT_ID); when(mockedEntity.getMetaData()).thenReturn(expectedMetaData); when(mockedEntity.getAction()).thenReturn(ActionVerb.NONE); entityPersistHandler.handle(mockedEntity, report, reportStats); verify(mockedEntityRepository, never()).create(expectedType, expectedMap, expectedMetaData, expectedType); } /** * @author tshewchuk 2/6/2010 (PI3 US811) * @author tke 3/15/2012 modified to test the new id normalization strategy * @param setId * : set entity ID if it is true. * Added testing of record DB lookup and update, and support for association * entities. */ public SimpleEntity createStudentEntity(boolean setId) { SimpleEntity entity = new SimpleEntity(); if (setId) { entity.setEntityId(STUDENT_ID); } entity.setStagedEntityId(STAGED_STUDENT_UUID); entity.setType("student"); Map<String, Object> field = new HashMap<String, Object>(); field.put("studentUniqueStateId", STUDENT_ID); field.put("Sex", "Male"); entity.setBody(field); entity.setMetaData(new HashMap<String, Object>()); return entity; } /** * @author tshewchuk 2/6/2010 (PI3 US811) * Added testing of record DB lookup and update, and support for association entities. */ public SimpleEntity createSchoolEntity() { // Create neutral record for entity. SimpleEntity entity = new SimpleEntity(); entity.setEntityId(SCHOOL_ID); entity.setType("school"); // Create new entity from neutral record. return entity; } /** * @author tshewchuk 2/6/2010 (PI3 US811) * @author tke 3/15/2012, modified to be consistent with the new ID normalization strategy * Added testing of record DB lookup and update, and support for association entities. */ public SimpleEntity createStudentSchoolAssociationEntity(String studentId, boolean setId) { SimpleEntity entity = new SimpleEntity(); if (setId) { entity.setEntityId(studentId); } entity.setStagedEntityId(STAGED_STUDENT_SCHOOL_UUID); entity.setType("studentSchoolAssociation"); Map<String, Object> localParentIds = new HashMap<String, Object>(); localParentIds.put("Student", studentId); localParentIds.put("School", SCHOOL_ID); entity.setMetaData(localParentIds); Map<String, Object> field = new HashMap<String, Object>(); field.put("studentId", studentId); field.put("schoolId", SCHOOL_ID); field.put("ClassOf", "2014"); entity.setBody(field); // Create and return new entity from neutral record. return entity; } public SimpleEntity createTeacherSchoolAssociationEntity(String teacherId, boolean setId) { // Create neutral record for entity. SimpleEntity entity = new SimpleEntity(); if (setId) { entity.setEntityId(teacherId); } entity.setStagedEntityId(STAGED_TEACHER_SCHOOL_UUID); entity.setType("teacherSchoolAssociation"); Map<String, Object> field = new HashMap<String, Object>(); field.put("teacherId", teacherId); field.put("schoolId", SCHOOL_ID); entity.setBody(field); entity.setMetaData(new HashMap<String, Object>()); // Create and return new entity from neutral record. return entity; } @Test public void testCreateTeacherSchoolAssociationEntity() { MongoEntityRepository entityRepository = mock(MongoEntityRepository.class); AbstractMessageReport errorReport = new DummyMessageReport(); ReportStats reportStats = new SimpleReportStats(); // Create a new student-school association entity, and test creating it in the data store. SimpleEntity foundTeacher = new SimpleEntity(); foundTeacher.setEntityId(INTERNAL_STUDENT_ID); LinkedList<Entity> teacherList = new LinkedList<Entity>(); teacherList.add(foundTeacher); // Teacher search. when(entityRepository.findAll(eq("teacher"), eq(regionIdStudentIdQuery))).thenReturn(teacherList); // School search. SimpleEntity foundSchool = new SimpleEntity(); foundSchool.setEntityId(INTERNAL_SCHOOL_ID); LinkedList<Entity> schoolList = new LinkedList<Entity>(); schoolList.add(foundSchool); when(entityRepository.findAll(eq("school"), eq(regionIdStudentIdQuery))).thenReturn(schoolList); SimpleEntity teacherSchoolAssociationEntity = createTeacherSchoolAssociationEntity(STUDENT_ID, false); entityPersistHandler.setEntityRepository(entityRepository); teacherSchoolAssociationEntity.getMetaData().put(EntityMetadataKey.TENANT_ID.getKey(), REGION_ID); entityPersistHandler.doHandling(teacherSchoolAssociationEntity, errorReport, reportStats); verify(entityRepository).createWithRetries(teacherSchoolAssociationEntity.getType(), null, teacherSchoolAssociationEntity.getBody(), teacherSchoolAssociationEntity.getMetaData(), teacherSchoolAssociationEntity.getType(), totalRetries); Assert.assertFalse("Error report should not contain errors", reportStats.hasErrors()); } }