All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.identityconnectors.contract.test.UpdateApiOpTests Maven / Gradle / Ivy

The newest version!
/*
 * ====================
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License("CDDL") (the "License").  You may not use this file
 * except in compliance with the License.
 *
 * You can obtain a copy of the License at
 * http://opensource.org/licenses/cddl1.php
 * See the License for the specific language governing permissions and limitations
 * under the License.
 *
 * When distributing the Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://opensource.org/licenses/cddl1.php.
 * If applicable, add the following below this CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 * ====================
 * Portions Copyrighted 2010-2013 ForgeRock AS.
 * Portions Copyrighted 2018 ConnId
 */
package org.identityconnectors.contract.test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import static org.junit.jupiter.api.Assertions.fail;

import java.text.MessageFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.identityconnectors.common.logging.Log;
import org.identityconnectors.contract.exceptions.ObjectNotFoundException;
import org.identityconnectors.framework.api.operations.APIOperation;
import org.identityconnectors.framework.api.operations.CreateApiOp;
import org.identityconnectors.framework.api.operations.DeleteApiOp;
import org.identityconnectors.framework.api.operations.GetApiOp;
import org.identityconnectors.framework.api.operations.UpdateApiOp;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeBuilder;
import org.identityconnectors.framework.common.objects.AttributeInfo;
import org.identityconnectors.framework.common.objects.AttributeUtil;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.Uid;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

/**
 * Contract test of {@link UpdateApiOp}.
 */
public class UpdateApiOpTests extends ObjectClassRunner {

    private static final Log LOG = Log.getLog(UpdateApiOpTests.class);

    protected static final String MODIFIED = "modified";

    private static final String ADDED = "added";

    public static final String TEST_NAME = "Update";

    private static final String NON_EXISTING_PROP_NAME = "unsupportedAttributeName";

    /**
     * {@inheritDoc}
     */
    @Override
    public Set> getAPIOperations() {
        Set> s = new HashSet<>();
        // list of required operations by this test:
        s.add(UpdateApiOp.class);
        s.add(CreateApiOp.class);
        s.add(GetApiOp.class);
        return s;
    }

    /**
     * {@inheritDoc}
     *
     * This tests an proper updating of attributes. All new attributes' values in given set for update are valid.
     *
     * Test procedure:
     * 1) Create object.
     * 2) Get this created object + fill it with values
     * 3) Get a set of new values for updateable attributes - replacing all attributes' values (no add or delete)
     * 4) Update uid and replaces values of attributes
     * 5) Verify if the object has new values of attributes
     * 6) ---- JUMPS TO FINALLY SECTION ---- see in TODOs
     */
    @Override
    protected void testRun(ObjectClass objectClass) {
        ConnectorObject obj = null;
        Uid uid = null;

        try {
            // create an object to update
            uid = ConnectorHelper.createObject(getConnectorFacade(), getDataProvider(),
                    getObjectClassInfo(objectClass), getTestName(), 0, getOperationOptionsByOp(objectClass,
                    CreateApiOp.class));
            assertNotNull(uid, "Create returned null Uid.");

            // get by uid
            obj = getConnectorFacade().getObject(objectClass, uid, getOperationOptionsByOp(objectClass, GetApiOp.class));
            assertNotNull(obj, "Cannot retrieve created object.");

            Set replaceAttributes = ConnectorHelper.getUpdateableAttributes(
                    getDataProvider(), getObjectClassInfo(objectClass), getTestName(), MODIFIED, 0, false,
                    false);

            if (replaceAttributes.size() > 0 || !isObjectClassSupported(objectClass)) {
                /* TODO when object class is not supported?!
                 */
                // update only in case there is something to update or when object class is not supported
                replaceAttributes.add(uid);

                assertTrue((replaceAttributes.size() > 0), "no update attributes were found");
                Uid newUid = getConnectorFacade().update(
                        objectClass, uid, AttributeUtil.filterUid(replaceAttributes), getOperationOptionsByOp(
                        objectClass, UpdateApiOp.class));

                // Update change of Uid must be propagated to replaceAttributes
                // set
                if (!newUid.equals(uid)) {
                    replaceAttributes.remove(uid);
                    replaceAttributes.add(newUid);
                    uid = newUid;
                }
            }

            // verify the change
            obj = getConnectorFacade().getObject(objectClass, uid,
                    getOperationOptionsByOp(objectClass, GetApiOp.class));
            assertNotNull(obj, "Cannot retrieve updated object.");
            ConnectorHelper.checkObject(getObjectClassInfo(objectClass), obj, replaceAttributes);
            // TODO Here it jumps to finally section which is wrong...
            // ADD and DELETE update test:
            // set of *multivalue* attributes with generated values
            Set addDelAttrs = ConnectorHelper.getUpdateableAttributes(getDataProvider(),
                    getObjectClassInfo(objectClass), getTestName(), ADDED, 0, false, true);
            if (addDelAttrs.size() > 0) {
                // uid must be present for update
                addDelAttrs.add(uid);
                Uid newUid = getConnectorFacade().addAttributeValues(objectClass,
                        uid,
                        AttributeUtil.filterUid(addDelAttrs), getOperationOptionsByOp(objectClass, UpdateApiOp.class));

                // Update change of Uid
                if (!newUid.equals(uid)) {
                    replaceAttributes.remove(uid);
                    addDelAttrs.remove(uid);
                    replaceAttributes.add(newUid);
                    addDelAttrs.add(newUid);
                    uid = newUid;
                }

                // verify the change after ADD
                obj = getConnectorFacade().getObject(objectClass, uid,
                        getOperationOptionsByOp(objectClass, GetApiOp.class));
                assertNotNull(obj, "Cannot retrieve updated object.");
                // don't want to have two same values for UID attribute
                addDelAttrs.remove(uid);
                ConnectorHelper.checkObject(getObjectClassInfo(objectClass), obj,
                        mergeAttributeSets(addDelAttrs, replaceAttributes));
                addDelAttrs.add(uid);

                // delete added attribute values
                newUid = getConnectorFacade().removeAttributeValues(objectClass,
                        uid,
                        AttributeUtil.filterUid(addDelAttrs), getOperationOptionsByOp(objectClass, UpdateApiOp.class));

                // Update change of Uid must be propagated to replaceAttributes
                if (!newUid.equals(uid)) {
                    replaceAttributes.remove(uid);
                    addDelAttrs.remove(uid);
                    replaceAttributes.add(newUid);
                    addDelAttrs.add(newUid);
                    uid = newUid;
                }

                // verify the change after DELETE
                obj = getConnectorFacade().getObject(objectClass, uid,
                        getOperationOptionsByOp(objectClass, GetApiOp.class));
                assertNotNull(obj, "Cannot retrieve updated object.");
                ConnectorHelper.checkObject(getObjectClassInfo(objectClass), obj, replaceAttributes);
            }
        } finally {
            if (uid != null) {
                // finally ... get rid of the object
                ConnectorHelper.deleteObject(getConnectorFacade(), objectClass, uid,
                        false, getOperationOptionsByOp(objectClass, DeleteApiOp.class));
            }
        }
    }

    /**
     * The test verifies that connector doesn't throw NullPointerException or some other unexpected behavior when passed
     * null as
     * attribute value. Test passes null values only for non-required non-special updateable attributes.
     *
     * Test procedure:
     * 1) Test if the required API operaions are supported by given objectClass.
     * 2) Set all attributes' values to null
     * If you want to skip some attributes from being set to null list them in
     * Update.updateToNullValue.skippedAttributes property
     * in groovy.config
     * 3) Try to update the object with property's value = null -> IF no exception, property has been removed or set to
     * null
     * 4) Check if the property has been really removed or set to null
     * 5) TODO something went wrong because the value has not been set to null value or removed. The value of nulled
     * property pesisted.
     *
     */
    @ParameterizedTest
    @MethodSource("objectClasses")
    public void testUpdateToNull(ObjectClass objectClass) {
        if (ConnectorHelper.operationsSupported(getConnectorFacade(), objectClass,
                getAPIOperations())) {
            ConnectorObject obj = null;
            Uid uid = null;

            try {
                // create an object to update
                uid = ConnectorHelper.createObject(getConnectorFacade(), getDataProvider(),
                        getObjectClassInfo(objectClass), getTestName(), 2, getOperationOptionsByOp(objectClass,
                        CreateApiOp.class));
                assertNotNull(uid, "Create returned null Uid.");

                ConnectorObject originalObject = getConnectorFacade().getObject(objectClass, uid,
                        getOperationOptionsByOp(objectClass, GetApiOp.class));

                Collection skippedAttributesForUpdateToNullValue = getSkippedAttributesForUpdateToNullValue();
                for (AttributeInfo attInfo : getObjectClassInfo(objectClass).getAttributeInfo()) {
                    if (attInfo.isUpdateable() && !attInfo.isRequired() && !AttributeUtil.isSpecial(attInfo)
                            && !attInfo.getType().isPrimitive()) {
                        if (skippedAttributesForUpdateToNullValue.contains(attInfo.getName())) {
                            LOG.info("Attribute '" + attInfo.getName() + "' was skipped in testUpdateToNull");
                            continue;
                        }
                        Set nullAttributes = new HashSet();
                        Attribute attr = AttributeBuilder.build(attInfo.getName());
                        nullAttributes.add(attr);

                        try {
                            Uid newUid = getConnectorFacade().update(
                                    objectClass, uid, nullAttributes,
                                    getOperationOptionsByOp(objectClass, UpdateApiOp.class));

                            LOG.info("No exception was thrown, attributes should be either removed or their values set"
                                    + " to null.");

                            // update uid
                            if (!uid.equals(newUid)) {
                                uid = newUid;
                            }

                            // verify the change
                            obj = getConnectorFacade().getObject(objectClass, uid,
                                    getOperationOptionsByOp(objectClass, GetApiOp.class));
                            assertNotNull(obj, "Cannot retrieve updated object.");

                            // check that nulled attributes were removed or set to null
                            if (ConnectorHelper.isReadable(getObjectClassInfo(objectClass), attr)) {
                                // null in case attribute is not present
                                Attribute checkedAttribute = obj.getAttributeByName(attInfo.getName());
                                String msg =
                                        "Attribute '%s' was neither removed nor its value set to null or empty list. "
                                        + "Updated value is : '%s'";
                                assertTrue(checkedAttribute == null || checkedAttribute.equals(attr)
                                        || checkedAttribute.getValue().isEmpty(),
                                        String.format(msg, attInfo.getName(), checkedAttribute != null
                                                ? checkedAttribute.getValue() : null));
                            }
                        } catch (RuntimeException ex) {
                            // ok, this option is possible in case connector cannot neither remove the attribute entirely
                            // nor set its value to null
                            // every RuntimeException except for NPE is possible
                            assertFalse(ex instanceof NullPointerException, String.format(
                                    "Update of attribute '%s' to null thrown NullPointerException.",
                                    attInfo.getName()));
                            LOG.info(String.format("RuntimeException was thrown when trying to update '%s' to null.",
                                    attInfo.getName()));
                        }
                    }
                }
            } finally {
                if (uid != null) {
                    // finally ... get rid of the object
                    ConnectorHelper.deleteObject(getConnectorFacade(), objectClass, uid,
                            false, getOperationOptionsByOp(objectClass, DeleteApiOp.class));
                }
            }
        } else {
            LOG.info(LOG_SEPARATOR);
            LOG.info("Skipping test ''testUpdateToNull'' for object class ''" + objectClass + "''.");
            LOG.info(LOG_SEPARATOR);
        }
    }

    /**
     * Tests create of two different objects and then update one to the same
     * attributes as the second. Test that updated object did not update uid to the same value as the first object.
     */
    @ParameterizedTest
    @MethodSource("objectClasses")
    public void testUpdateToSameAttributes(ObjectClass objectClass) {
        if (ConnectorHelper.operationsSupported(getConnectorFacade(), objectClass, getAPIOperations())) {
            Uid uid1 = null;
            Uid uid2 = null;

            try {
                // create two new objects
                Set attrs1 = ConnectorHelper.getCreateableAttributes(getDataProvider(),
                        getObjectClassInfo(objectClass), getTestName(), 1, true, false);
                uid1 = getConnectorFacade().create(objectClass, attrs1, null);
                assertNotNull(uid1, "Create returned null uid.");

                // get the object to make sure it exist now
                ConnectorObject obj1 = getConnectorFacade().getObject(objectClass,
                        uid1, getOperationOptionsByOp(objectClass, GetApiOp.class));

                // compare requested attributes to retrieved attributes
                ConnectorHelper.checkObject(getObjectClassInfo(objectClass), obj1, attrs1);

                Set attrs2 = ConnectorHelper.getCreateableAttributes(getDataProvider(),
                        getObjectClassInfo(objectClass), getTestName(), 2, true, false);
                uid2 = getConnectorFacade().create(objectClass, attrs2, null);
                assertNotNull(uid2, "Create returned null uid.");

                // get the object to make sure it exist now
                ConnectorObject obj2 = getConnectorFacade().getObject(objectClass,
                        uid2, getOperationOptionsByOp(objectClass, GetApiOp.class));

                // compare requested attributes to retrieved attributes
                ConnectorHelper.checkObject(getObjectClassInfo(objectClass), obj2, attrs2);

                // update second object with updateable attributes of first object
                Set replaceAttributes = new HashSet<>();
                attrs1.stream().
                        filter((attr) -> (ConnectorHelper.isUpdateable(getObjectClassInfo(objectClass), attr))).
                        forEachOrdered((attr) -> {
                            replaceAttributes.add(attr);
                        });
                replaceAttributes.add(uid2);

                try {
                    Uid newUid = getConnectorFacade().update(
                            objectClass, uid2, AttributeUtil.filterUid(replaceAttributes), getOperationOptionsByOp(
                            objectClass, UpdateApiOp.class));

                    if (!uid2.equals(newUid)) {
                        uid2 = newUid;
                    }

                    assertFalse(uid1.equals(uid2), "Update returned the same uid when tried to update to the same "
                            + "attributes as another object.");
                } catch (RuntimeException ex) {
                    // ok - update could throw this in case __NAME__ and __UID__ are the same attributes
                }
            } finally {
                if (uid1 != null) {
                    // delete the object
                    ConnectorHelper.deleteObject(getConnectorFacade(), objectClass, uid1,
                            false, getOperationOptionsByOp(objectClass, DeleteApiOp.class));
                }
                if (uid2 != null) {
                    // delete the object
                    ConnectorHelper.deleteObject(getConnectorFacade(), objectClass, uid2,
                            false, getOperationOptionsByOp(objectClass, DeleteApiOp.class));
                }
            }
        } else {
            LOG.info(LOG_SEPARATOR);
            LOG.info("Skipping test ''testUpdateToSameAttributes'' for object class ''" + objectClass + "''.");
            LOG.info(LOG_SEPARATOR);
        }
    }

    @Override
    public String getTestName() {
        return TEST_NAME;
    }

    /**
     * Tests update method with invalid Attribute, RuntimeException is expected
     *
     * connector developers can set the value of unsupported attribute
     * using test property: testsuite.Create.unsupportedAttributeName
     */
    @ParameterizedTest
    @MethodSource("objectClasses")
    public void testUpdateFailUnsupportedAttribute(ObjectClass objectClass) {
        // run the contract test only if update is supported by tested object class
        if (ConnectorHelper.operationsSupported(getConnectorFacade(),
                objectClass, getAPIOperations())) {

            Uid uid = null;
            try {
                // create an object to update
                uid = ConnectorHelper.createObject(getConnectorFacade(),
                        getDataProvider(), getObjectClassInfo(objectClass), getTestName(),
                        0, getOperationOptionsByOp(objectClass, CreateApiOp.class));
                assertNotNull(uid, "Create returned null Uid.");

                // get by uid
                ConnectorObject obj = getConnectorFacade().getObject(
                        objectClass, uid,
                        getOperationOptionsByOp(objectClass, GetApiOp.class));
                assertNotNull(obj, "Cannot retrieve created object.");

                Set replaceAttributes = ConnectorHelper
                        .getUpdateableAttributes(getDataProvider(),
                                getObjectClassInfo(objectClass), getTestName(), MODIFIED,
                                0, false, false);

                if (replaceAttributes.size() > 0 || !isObjectClassSupported(objectClass)) {
                    // update only in case there is something to update or when
                    // object class is not supported
                    replaceAttributes.add(uid);

                    String unsupportedAttribute = null;
                    try {
                        unsupportedAttribute = (String) getDataProvider()
                                .getTestSuiteAttribute(NON_EXISTING_PROP_NAME,
                                        TEST_NAME);
                    } catch (ObjectNotFoundException ex) {
                        unsupportedAttribute = "nonExistingAndUnlikelyAttrName";
                    }
                    // + add one non-existing attribute
                    replaceAttributes.add(AttributeBuilder
                            .build(unsupportedAttribute));

                    assertTrue((replaceAttributes.size() > 0), "no update attributes were found");

                    Uid uidNew = null;
                    try {
                        uidNew = getConnectorFacade().update(objectClass,
                                uid,
                                AttributeUtil.filterUid(replaceAttributes),
                                null);
                        fail("'testUpdateFailUnsupportedAttribute': NONEXISTING attribute accepted without throwing "
                                + "a RuntimeException.");
                    } catch (RuntimeException ex) {
                        // ok
                    } finally {
                        if (uidNew != null) {
                            // delete the created the object
                            ConnectorHelper.deleteObject(getConnectorFacade(),
                                    objectClass, uidNew, false,
                                    getOperationOptionsByOp(objectClass, DeleteApiOp.class));
                        }
                    }
                }
            } finally {
                if (uid != null) {
                    // delete the created the object
                    ConnectorHelper.deleteObject(getConnectorFacade(),
                            objectClass, uid, false,
                            getOperationOptionsByOp(objectClass, DeleteApiOp.class));
                }
            }
        } else {
            String msg =
                    "Skipping test ''testUpdateFailUnsupportedAttribute'' for object class ''" + objectClass + "''.";
            LOG.info(LOG_SEPARATOR);
            LOG.info(msg);
            LOG.info(LOG_SEPARATOR);
        }
    }

    /**
     * Returns new attribute set which contains all attributes from both sets. If attribute with the same name is
     * present
     * in both sets then its values are merged.
     */
    protected static Set mergeAttributeSets(Set attrSet1, Set attrSet2) {
        Set attrs = new HashSet<>();
        Map attrMap2 = new HashMap<>();
        attrSet2.forEach((attr) -> {
            attrMap2.put(attr.getName(), attr);
        });

        attrSet1.forEach((attr1) -> {
            Attribute attr2 = attrMap2.remove(attr1.getName());
            // if attribute is present in both sets then merge its values
            if (attr2 != null) {
                AttributeBuilder attrBuilder = new AttributeBuilder();
                attrBuilder.setName(attr1.getName());
                attrBuilder.addValue(attr1.getValue());
                attrBuilder.addValue(attr2.getValue());
                attrs.add(attrBuilder.build());
            } else {
                attrs.add(attr1);
            }
        });

        // add remaining attributes from second set
        attrMap2.values().forEach((attr2) -> {
            attrs.add(attr2);
        });

        return attrs;
    }

    @SuppressWarnings("unchecked")
    protected static Collection getSkippedAttributesForUpdateToNullValue() {
        Object skippedAttributes = null;
        try {
            skippedAttributes = getDataProvider().
                    getTestSuiteAttribute("updateToNullValue.skippedAttributes", TEST_NAME);
        } catch (ObjectNotFoundException e) {
        }
        if (skippedAttributes == null) {
            return Collections.emptyList();
        }
        if (!(skippedAttributes instanceof Collection)) {
            throw new RuntimeException(MessageFormat.format(
                    "Testsuite Property '{0}' must be of type Collection , but was of type {1}", "testsuite."
                    + TEST_NAME
                    + "." + "updateToNullValue.skippedAttributes", skippedAttributes.getClass()));
        }
        return (Collection) (skippedAttributes);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy