/* * Copyright (c) 2010-2016 Evolveum * * 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 com.evolveum.midpoint.model.intest.sync; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertTrue; import static com.evolveum.midpoint.test.IntegrationTestTools.display; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertNotNull; import static org.testng.AssertJUnit.assertNull; import java.io.File; import java.util.Iterator; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import com.evolveum.icf.dummy.resource.DummyAccount; import com.evolveum.icf.dummy.resource.DummyResource; import com.evolveum.midpoint.audit.api.AuditEventRecord; import com.evolveum.midpoint.audit.api.AuditEventStage; import com.evolveum.midpoint.audit.api.AuditEventType; import com.evolveum.midpoint.model.impl.sync.ReconciliationTaskHandler; import com.evolveum.midpoint.model.impl.util.DebugReconciliationTaskResultListener; import com.evolveum.midpoint.model.intest.AbstractInitializedModelIntegrationTest; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.schema.constants.MidPointConstants; import com.evolveum.midpoint.schema.internals.InternalMonitor; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.test.DummyResourceContoller; import com.evolveum.midpoint.test.util.TestUtil; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentPolicyEnforcementType; import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; /** * @author semancik * */ @ContextConfiguration(locations = {"classpath:ctx-model-intest-test-main.xml"}) @Listeners({ com.evolveum.midpoint.tools.testng.AlphabeticalMethodInterceptor.class }) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) public class TestUuid extends AbstractInitializedModelIntegrationTest { private static final File TEST_DIR = new File("src/test/resources/sync"); protected static final File RESOURCE_DUMMY_UUID_FILE = new File(TEST_DIR, "resource-dummy-uuid.xml"); protected static final String RESOURCE_DUMMY_UUID_OID = "9792acb2-0b75-11e5-b66e-001e8c717e5b"; protected static final String RESOURCE_DUMMY_UUID_NAME = "uuid"; protected static final String RESOURCE_DUMMY_UUID_NAMESPACE = MidPointConstants.NS_RI; protected static final File TASK_RECONCILE_DUMMY_UUID_FILE = new File(TEST_DIR, "task-reconcile-dummy-uuid.xml"); protected static final String TASK_RECONCILE_DUMMY_UUID_OID = "98ae26fc-0b76-11e5-b943-001e8c717e5b"; private static final String USER_AUGUSTUS_NAME = "augustus"; private static final String ACCOUNT_AUGUSTUS_NAME = "augustus"; private static final String ACCOUNT_AUGUSTUS_FULLNAME = "Augustus DeWaat"; private static final String ACCOUNT_AUGUSTINA_NAME = "augustina"; private static final String ACCOUNT_AUGUSTINA_FULLNAME = "Augustina LeWhat"; protected DummyResource dummyResourceUuid; protected DummyResourceContoller dummyResourceCtlUuid; protected ResourceType resourceDummyUuidType; protected PrismObject<ResourceType> resourceDummyUuid; private String augustusShadowOid; @Autowired(required=true) private ReconciliationTaskHandler reconciliationTaskHandler; private DebugReconciliationTaskResultListener reconciliationTaskResultListener; @Override public void initSystem(Task initTask, OperationResult initResult) throws Exception { super.initSystem(initTask, initResult); reconciliationTaskResultListener = new DebugReconciliationTaskResultListener(); reconciliationTaskHandler.setReconciliationTaskResultListener(reconciliationTaskResultListener); dummyResourceCtlUuid = DummyResourceContoller.create(RESOURCE_DUMMY_UUID_NAME, resourceDummyUuid); dummyResourceCtlUuid.extendSchemaPirate(); dummyResourceUuid = dummyResourceCtlUuid.getDummyResource(); resourceDummyUuid = importAndGetObjectFromFile(ResourceType.class, RESOURCE_DUMMY_UUID_FILE, RESOURCE_DUMMY_UUID_OID, initTask, initResult); resourceDummyUuidType = resourceDummyUuid.asObjectable(); dummyResourceCtlUuid.setResource(resourceDummyUuid); InternalMonitor.reset(); InternalMonitor.setTraceShadowFetchOperation(true); // DebugUtil.setDetailedDebugDump(true); } @Test public void test200ReconcileDummyUuid() throws Exception { final String TEST_NAME = "test200ReconcileDummyUuid"; TestUtil.displayTestTile(this, TEST_NAME); // GIVEN Task task = createTask(TestUuid.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); assumeAssignmentPolicy(AssignmentPolicyEnforcementType.NONE); // TODO getDummyResource().purgeScriptHistory(); dummyAuditService.clear(); rememberShadowFetchOperationCount(); reconciliationTaskResultListener.clear(); // WHEN TestUtil.displayWhen(TEST_NAME); importObjectFromFile(TASK_RECONCILE_DUMMY_UUID_FILE); // THEN TestUtil.displayThen(TEST_NAME); waitForTaskFinish(TASK_RECONCILE_DUMMY_UUID_OID, false); // THEN TestUtil.displayThen(TEST_NAME); reconciliationTaskResultListener.assertResult(RESOURCE_DUMMY_UUID_OID, 0, 0, 0, 0); List<PrismObject<UserType>> users = modelService.searchObjects(UserType.class, null, null, task, result); display("Users after import", users); assertImportedUserByOid(USER_ADMINISTRATOR_OID); assertImportedUserByOid(USER_JACK_OID); assertImportedUserByOid(USER_BARBOSSA_OID); assertEquals("Unexpected number of users", 5, users.size()); display("Dummy resource", getDummyResource().debugDump()); assertReconAuditModifications(0, TASK_RECONCILE_DUMMY_UUID_OID); // Task result PrismObject<TaskType> reconTaskAfter = getTask(TASK_RECONCILE_DUMMY_UUID_OID); OperationResultType reconTaskResult = reconTaskAfter.asObjectable().getResult(); display("Recon task result", reconTaskResult); TestUtil.assertSuccess(reconTaskResult); } @Test public void test210ReconcileDummyUuidAddAugustus() throws Exception { final String TEST_NAME = "test210ReconcileDummyUuidAddAugustus"; TestUtil.displayTestTile(this, TEST_NAME); // GIVEN Task task = createTask(TestUuid.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); assumeAssignmentPolicy(AssignmentPolicyEnforcementType.NONE); DummyAccount account = new DummyAccount(ACCOUNT_AUGUSTUS_NAME); account.setEnabled(true); account.addAttributeValues(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_FULLNAME_NAME, ACCOUNT_AUGUSTUS_FULLNAME); dummyResourceUuid.addAccount(account); getDummyResource().purgeScriptHistory(); dummyAuditService.clear(); rememberShadowFetchOperationCount(); reconciliationTaskResultListener.clear(); Task taskBefore = taskManager.getTask(TASK_RECONCILE_DUMMY_UUID_OID, result); // WHEN TestUtil.displayWhen(TEST_NAME); restartTask(TASK_RECONCILE_DUMMY_UUID_OID); // THEN TestUtil.displayThen(TEST_NAME); waitForTaskNextRunAssertSuccess(taskBefore, true); // THEN TestUtil.displayThen(TEST_NAME); reconciliationTaskResultListener.assertResult(RESOURCE_DUMMY_UUID_OID, 0, 1, 0, 0); List<PrismObject<UserType>> users = modelService.searchObjects(UserType.class, null, null, task, result); display("Users after import", users); assertImportedUserByOid(USER_ADMINISTRATOR_OID); assertImportedUserByOid(USER_JACK_OID); assertImportedUserByOid(USER_BARBOSSA_OID); assertImportedUserByUsername(ACCOUNT_AUGUSTUS_NAME, RESOURCE_DUMMY_UUID_OID); assertDummyAccountAttribute(RESOURCE_DUMMY_UUID_NAME, ACCOUNT_AUGUSTUS_NAME, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_FULLNAME_NAME, ACCOUNT_AUGUSTUS_FULLNAME); assertEquals("Unexpected number of users", 6, users.size()); display("Dummy resource", getDummyResource().debugDump()); assertReconAuditModifications(1, TASK_RECONCILE_DUMMY_UUID_OID); // Task result PrismObject<TaskType> reconTaskAfter = getTask(TASK_RECONCILE_DUMMY_UUID_OID); OperationResultType reconTaskResult = reconTaskAfter.asObjectable().getResult(); display("Recon task result", reconTaskResult); TestUtil.assertSuccess(reconTaskResult); PrismObject<UserType> user = findUserByUsername(USER_AUGUSTUS_NAME); display("Augustus after recon", user); augustusShadowOid = getSingleLinkOid(user); PrismObject<ShadowType> repoShadow = repositoryService.getObject(ShadowType.class, augustusShadowOid, null, result); } /** * Augustus is deleted and re-added with the same username. New shadow needs to be created. */ @Test public void test220ReconcileDummyUuidDeleteAddAugustus() throws Exception { final String TEST_NAME = "test220ReconcileDummyUuidDeleteAddAugustus"; TestUtil.displayTestTile(this, TEST_NAME); // GIVEN Task task = createTask(TestUuid.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); assumeAssignmentPolicy(AssignmentPolicyEnforcementType.NONE); DummyAccount oldAccount = dummyResourceUuid.getAccountByUsername(ACCOUNT_AUGUSTUS_NAME); dummyResourceUuid.deleteAccountByName(ACCOUNT_AUGUSTUS_NAME); assertNoDummyAccount(ACCOUNT_AUGUSTUS_NAME, ACCOUNT_AUGUSTUS_NAME); DummyAccount account = new DummyAccount(ACCOUNT_AUGUSTUS_NAME); account.setEnabled(true); account.addAttributeValues(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_FULLNAME_NAME, ACCOUNT_AUGUSTUS_FULLNAME); account.addAttributeValues(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME, USER_AUGUSTUS_NAME); dummyResourceUuid.addAccount(account); account = dummyResourceUuid.getAccountByUsername(ACCOUNT_AUGUSTUS_NAME); assertFalse("Account IDs not changed", oldAccount.getId().equals(account.getId())); display("Old shadow OID", augustusShadowOid); display("Account ID "+ oldAccount.getId() + " -> " + account.getId()); Task taskBefore = taskManager.getTask(TASK_RECONCILE_DUMMY_UUID_OID, result); getDummyResource().purgeScriptHistory(); dummyAuditService.clear(); rememberShadowFetchOperationCount(); reconciliationTaskResultListener.clear(); // WHEN TestUtil.displayWhen(TEST_NAME); restartTask(TASK_RECONCILE_DUMMY_UUID_OID); // THEN TestUtil.displayThen(TEST_NAME); waitForTaskNextRunAssertSuccess(taskBefore, true); // THEN TestUtil.displayThen(TEST_NAME); reconciliationTaskResultListener.assertResult(RESOURCE_DUMMY_UUID_OID, 0, 1, 0, 0); List<PrismObject<UserType>> users = modelService.searchObjects(UserType.class, null, null, task, result); display("Users after import", users); assertImportedUserByOid(USER_ADMINISTRATOR_OID); assertImportedUserByOid(USER_JACK_OID); assertImportedUserByOid(USER_BARBOSSA_OID); assertImportedUserByUsername(ACCOUNT_AUGUSTUS_NAME, RESOURCE_DUMMY_UUID_OID); assertDummyAccountAttribute(RESOURCE_DUMMY_UUID_NAME, ACCOUNT_AUGUSTUS_NAME, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_FULLNAME_NAME, ACCOUNT_AUGUSTUS_FULLNAME); assertEquals("Unexpected number of users", 6, users.size()); display("Dummy resource", getDummyResource().debugDump()); assertReconAuditModifications(1, TASK_RECONCILE_DUMMY_UUID_OID); // Task result PrismObject<TaskType> reconTaskAfter = getTask(TASK_RECONCILE_DUMMY_UUID_OID); OperationResultType reconTaskResult = reconTaskAfter.asObjectable().getResult(); display("Recon task result", reconTaskResult); TestUtil.assertSuccess(reconTaskResult); PrismObject<UserType> user = findUserByUsername(USER_AUGUSTUS_NAME); display("Augustus after recon", user); String newAugustusShadowOid = getSingleLinkOid(user); PrismObject<ShadowType> repoShadow = repositoryService.getObject(ShadowType.class, newAugustusShadowOid, null, result); assertFalse("Shadow OID is not changed", augustusShadowOid.equals(newAugustusShadowOid)); augustusShadowOid = newAugustusShadowOid; } /** * Augustus is deleted and Augustina re-added. They correlate to the same user. * New shadow needs to be created. */ @Test public void test230ReconcileDummyUuidDeleteAugustusAddAugustina() throws Exception { final String TEST_NAME = "test230ReconcileDummyUuidDeleteAugustusAddAugustina"; TestUtil.displayTestTile(this, TEST_NAME); // GIVEN Task task = createTask(TestUuid.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); assumeAssignmentPolicy(AssignmentPolicyEnforcementType.NONE); DummyAccount oldAccount = dummyResourceUuid.getAccountByUsername(ACCOUNT_AUGUSTUS_NAME); dummyResourceUuid.deleteAccountByName(ACCOUNT_AUGUSTUS_NAME); assertNoDummyAccount(ACCOUNT_AUGUSTUS_NAME, ACCOUNT_AUGUSTUS_NAME); DummyAccount account = new DummyAccount(ACCOUNT_AUGUSTINA_NAME); account.setEnabled(true); account.addAttributeValues(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_FULLNAME_NAME, ACCOUNT_AUGUSTINA_FULLNAME); account.addAttributeValues(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME, USER_AUGUSTUS_NAME); dummyResourceUuid.addAccount(account); account = dummyResourceUuid.getAccountByUsername(ACCOUNT_AUGUSTINA_NAME); assertFalse("Account IDs not changed", oldAccount.getId().equals(account.getId())); display("Old shadow OID", augustusShadowOid); display("Account ID "+ oldAccount.getId() + " -> " + account.getId()); Task taskBefore = taskManager.getTask(TASK_RECONCILE_DUMMY_UUID_OID, result); getDummyResource().purgeScriptHistory(); dummyAuditService.clear(); rememberShadowFetchOperationCount(); reconciliationTaskResultListener.clear(); // WHEN TestUtil.displayWhen(TEST_NAME); restartTask(TASK_RECONCILE_DUMMY_UUID_OID); // THEN TestUtil.displayThen(TEST_NAME); waitForTaskNextRunAssertSuccess(taskBefore, true); // THEN TestUtil.displayThen(TEST_NAME); reconciliationTaskResultListener.assertResult(RESOURCE_DUMMY_UUID_OID, 0, 1, 0, 0); List<PrismObject<UserType>> users = modelService.searchObjects(UserType.class, null, null, task, result); display("Users after import", users); assertImportedUserByOid(USER_ADMINISTRATOR_OID); assertImportedUserByOid(USER_JACK_OID); assertImportedUserByOid(USER_BARBOSSA_OID); assertImportedUserByUsername(USER_AUGUSTUS_NAME, RESOURCE_DUMMY_UUID_OID); assertDummyAccountAttribute(RESOURCE_DUMMY_UUID_NAME, ACCOUNT_AUGUSTINA_NAME, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_FULLNAME_NAME, ACCOUNT_AUGUSTINA_FULLNAME); assertEquals("Unexpected number of users", 6, users.size()); display("Dummy resource", getDummyResource().debugDump()); assertReconAuditModifications(1, TASK_RECONCILE_DUMMY_UUID_OID); // Task result PrismObject<TaskType> reconTaskAfter = getTask(TASK_RECONCILE_DUMMY_UUID_OID); OperationResultType reconTaskResult = reconTaskAfter.asObjectable().getResult(); display("Recon task result", reconTaskResult); TestUtil.assertSuccess(reconTaskResult); PrismObject<UserType> user = findUserByUsername(USER_AUGUSTUS_NAME); display("Augustus after recon", user); String newAugustusShadowOid = getSingleLinkOid(user); PrismObject<ShadowType> repoShadow = repositoryService.getObject(ShadowType.class, newAugustusShadowOid, null, result); assertFalse("Shadow OID is not changed", augustusShadowOid.equals(newAugustusShadowOid)); } private void assertReconAuditModifications(int expectedModifications, String taskOid) { // Check audit display("Audit", dummyAuditService); List<AuditEventRecord> auditRecords = dummyAuditService.getRecords(); Iterator<AuditEventRecord> iterator = auditRecords.iterator(); while (iterator.hasNext()) { AuditEventRecord record = iterator.next(); if (record.getTaskOID() != null && !record.getTaskOID().equals(taskOid)) { // Record from some other task, skip it iterator.remove(); } } int i=0; while (i < (auditRecords.size() - 1)) { AuditEventRecord reconStartRecord = auditRecords.get(i); if (reconStartRecord.getEventType() == AuditEventType.EXECUTE_CHANGES_RAW) { i++; continue; } assertNotNull("No reconStartRecord audit record", reconStartRecord); assertEquals("Wrong stage in reconStartRecord audit record: "+reconStartRecord, AuditEventStage.REQUEST, reconStartRecord.getEventStage()); assertEquals("Wrong type in reconStartRecord audit record: "+reconStartRecord, AuditEventType.RECONCILIATION, reconStartRecord.getEventType()); assertTrue("Unexpected delta in reconStartRecord audit record "+reconStartRecord, reconStartRecord.getDeltas() == null || reconStartRecord.getDeltas().isEmpty()); i++; break; } int modifications = 0; for (; i < (auditRecords.size() - 1); i+=2) { AuditEventRecord requestRecord = auditRecords.get(i); assertNotNull("No request audit record ("+i+")", requestRecord); if (requestRecord.getEventStage() == AuditEventStage.EXECUTION && requestRecord.getEventType() == AuditEventType.RECONCILIATION) { // end of audit records; break; } assertEquals("Got this instead of request audit record ("+i+"): "+requestRecord, AuditEventStage.REQUEST, requestRecord.getEventStage()); // Request audit may or may not have a delta. Usual records will not have a delta. But e.g. disableAccount reactions will have. AuditEventRecord executionRecord = auditRecords.get(i+1); assertNotNull("No execution audit record (" + i + ")", executionRecord); assertEquals("Got this instead of execution audit record (" + i + "): " + executionRecord, AuditEventStage.EXECUTION, executionRecord.getEventStage()); assertTrue("Empty deltas in execution audit record "+executionRecord, executionRecord.getDeltas() != null && ! executionRecord.getDeltas().isEmpty()); modifications++; while (i+2 < auditRecords.size()) { AuditEventRecord nextRecord = auditRecords.get(i+2); if (nextRecord.getEventStage() == AuditEventStage.EXECUTION && nextRecord.getEventType() == requestRecord.getEventType()) { // this is an additional EXECUTION record due to changes in clockwork i++; } else { break; } } } assertEquals("Unexpected number of audit modifications", expectedModifications, modifications); AuditEventRecord reconStopRecord = auditRecords.get(i); assertNotNull("No reconStopRecord audit record", reconStopRecord); assertEquals("Wrong stage in reconStopRecord audit record: "+reconStopRecord, AuditEventStage.EXECUTION, reconStopRecord.getEventStage()); assertEquals("Wrong type in reconStopRecord audit record: "+reconStopRecord, AuditEventType.RECONCILIATION, reconStopRecord.getEventType()); assertTrue("Unexpected delta in reconStopRecord audit record "+reconStopRecord, reconStopRecord.getDeltas() == null || reconStopRecord.getDeltas().isEmpty()); } private void assertNoImporterUserByUsername(String username) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException { PrismObject<UserType> user = findUserByUsername(username); assertNull("User "+username+" sneaked in", user); } private void assertImportedUserByOid(String userOid, String... resourceOids) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException { PrismObject<UserType> user = getUser(userOid); assertNotNull("No user "+userOid, user); assertImportedUser(user, resourceOids); } private void assertImportedUserByUsername(String username, String... resourceOids) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException { PrismObject<UserType> user = findUserByUsername(username); assertNotNull("No user "+username, user); assertImportedUser(user, resourceOids); } private void assertImportedUser(PrismObject<UserType> user, String... resourceOids) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException { assertLinks(user, resourceOids.length); for (String resourceOid: resourceOids) { assertAccount(user, resourceOid); } assertAdministrativeStatusEnabled(user); } }