/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2011-2015 ForgeRock AS.
*/
package org.forgerock.openidm.provisioner.openicf.impl;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.forgerock.json.JsonValue.field;
import static org.forgerock.json.JsonValue.json;
import static org.forgerock.json.JsonValue.object;
import static org.forgerock.json.resource.Responses.newActionResponse;
import static org.forgerock.json.resource.Router.uriTemplate;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.FilenameFilter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.tuple.Pair;
import org.forgerock.openidm.router.IDMConnectionFactoryWrapper;
import org.forgerock.services.context.Context;
import org.forgerock.services.context.RootContext;
import org.forgerock.services.routing.RouteMatcher;
import org.forgerock.json.JsonPointer;
import org.forgerock.json.JsonValue;
import org.forgerock.json.resource.ActionRequest;
import org.forgerock.json.resource.ActionResponse;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.ConflictException;
import org.forgerock.json.resource.Connection;
import org.forgerock.json.resource.CreateRequest;
import org.forgerock.json.resource.DeleteRequest;
import org.forgerock.json.resource.ForbiddenException;
import org.forgerock.json.resource.InternalServerErrorException;
import org.forgerock.json.resource.MemoryBackend;
import org.forgerock.json.resource.NotFoundException;
import org.forgerock.json.resource.NotSupportedException;
import org.forgerock.json.resource.PatchOperation;
import org.forgerock.json.resource.PatchRequest;
import org.forgerock.json.resource.PermanentException;
import org.forgerock.json.resource.PreconditionFailedException;
import org.forgerock.json.resource.PreconditionRequiredException;
import org.forgerock.json.resource.QueryRequest;
import org.forgerock.json.resource.QueryResourceHandler;
import org.forgerock.json.resource.QueryResponse;
import org.forgerock.json.resource.ReadRequest;
import org.forgerock.json.resource.Requests;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ResourceResponse;
import org.forgerock.json.resource.Resources;
import org.forgerock.json.resource.Router;
import org.forgerock.services.context.SecurityContext;
import org.forgerock.json.resource.ServiceUnavailableException;
import org.forgerock.json.resource.SingletonResourceProvider;
import org.forgerock.json.resource.SortKey;
import org.forgerock.json.resource.UpdateRequest;
import org.forgerock.openicf.framework.ConnectorFrameworkFactory;
import org.forgerock.openidm.audit.util.NullActivityLogger;
import org.forgerock.openidm.config.enhanced.JSONEnhancedConfig;
import org.forgerock.openidm.core.IdentityServer;
import org.forgerock.openidm.core.PropertyAccessor;
import org.forgerock.openidm.provisioner.impl.SystemObjectSetService;
import org.forgerock.openidm.provisioner.openicf.commons.ConnectorUtil;
import org.forgerock.openidm.provisioner.openicf.internal.SystemAction;
import org.forgerock.openidm.provisioner.openicf.syncfailure.NullSyncFailureHandler;
import org.forgerock.openidm.provisioner.openicf.syncfailure.SyncFailureHandler;
import org.forgerock.openidm.provisioner.openicf.syncfailure.SyncFailureHandlerFactory;
import org.forgerock.openidm.router.RouteBuilder;
import org.forgerock.openidm.router.RouteEntry;
import org.forgerock.openidm.router.RouteService;
import org.forgerock.openidm.router.RouterRegistry;
import org.forgerock.openidm.util.FileUtil;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.query.QueryFilter;
import org.identityconnectors.common.logging.Log;
import org.identityconnectors.common.logging.impl.NoOpLogger;
import org.identityconnectors.framework.common.objects.Name;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.OperationOptions;
import org.identityconnectors.framework.common.objects.SyncToken;
import org.identityconnectors.framework.common.objects.Uid;
import org.identityconnectors.framework.server.ConnectorServer;
import org.identityconnectors.framework.server.impl.ConnectorServerImpl;
import org.identityconnectors.framework.spi.operations.UpdateAttributeValuesOp;
import org.identityconnectors.framework.spi.operations.UpdateOp;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
/**
* A NAME does ...
*
*/
public class OpenICFProvisionerServiceTest implements RouterRegistry, SyncFailureHandlerFactory {
public static final String LAUNCHER_INSTALL_LOCATION = "launcher.install.location";
public static final String LAUNCHER_INSTALL_URL = "launcher.install.url";
public static final String LAUNCHER_WORKING_LOCATION = "launcher.working.location";
public static final String LAUNCHER_WORKING_URL = "launcher.working.url";
public static final String LAUNCHER_PROJECT_LOCATION = "launcher.project.location";
public static final String LAUNCHER_PROJECT_URL = "launcher.project.url";
/* @formatter:off */
private static final String CONFIGURATION_TEMPLATE =
"{\n" +
" \"connectorsLocation\" : \"connectors\",\n" +
" \"remoteConnectorServers\" : [\n" +
" {\n" +
" \"name\" : \"testServer\",\n" +
" \"host\" : \"127.0.0.1\",\n" +
" \"_port\" : \"${openicfServerPort}\",\n" +
" \"port\" : 8759,\n" +
" \"useSSL\" : false,\n" +
" \"timeout\" : 0,\n" +
" \"key\" : \"Passw0rd\",\n" +
" \"heartbeatInterval\" : 5\n" +
" }\n" +
" ]\n" +
"}";
/* @formatter:on */
/**
* Setup logging for the {@link OpenICFProvisionerServiceTest}.
*/
private final static Logger logger = LoggerFactory
.getLogger(OpenICFProvisionerServiceTest.class);
private Connection connection = null;
private ConnectorServer connectorServer = null;
private Pair<ConnectorInfoProviderService, ComponentContext> provider = null;
private final List<Pair<OpenICFProvisionerService, ComponentContext>> systems =
new ArrayList<Pair<OpenICFProvisionerService, ComponentContext>>();
protected final Router router = new Router();
final RouteService routeService = new RouteService() {
};
public OpenICFProvisionerServiceTest() {
try {
IdentityServer.initInstance(new PropertyAccessor() {
@Override
public <T> T getProperty(String key, T defaultValue, Class<T> expected) {
if (String.class.isAssignableFrom(expected)) {
try {
if (LAUNCHER_INSTALL_LOCATION.equals(key)
|| LAUNCHER_PROJECT_LOCATION.equals(key)
|| LAUNCHER_WORKING_LOCATION.equals(key)) {
return (T) URLDecoder.decode(OpenICFProvisionerServiceTest.class
.getResource("/").getPath(), "utf-8");
} else if (LAUNCHER_INSTALL_URL.equals(key)
|| LAUNCHER_PROJECT_URL.equals(key)
|| LAUNCHER_WORKING_URL.equals(key)) {
return (T) OpenICFProvisionerServiceTest.class.getResource("/")
.toString();
}
} catch (UnsupportedEncodingException e) {
/* ignore */
}
}
return null;
}
});
router.addRoute(uriTemplate("repo/synchronisation/pooledSyncStage"), new MemoryBackend());
router.addRoute(uriTemplate("audit/activity"), new MemoryBackend());
} catch (IllegalStateException e) {
/* ignore */
}
}
// ----- Implementation of RouterRegistry interface
@Override
public RouteEntry addRoute(RouteBuilder routeBuilder) {
final RouteMatcher[] routes = routeBuilder.register(router);
return new RouteEntry() {
@Override
public boolean removeRoute() {
return router.removeRoute(routes);
}
};
}
@DataProvider(name = "dp")
public Iterator<Object[]> createData() throws Exception {
List<Object[]> tests = new ArrayList<Object[]>();
for (Pair<OpenICFProvisionerService, ComponentContext> pair : systems) {
tests.add(new Object[] { pair.getLeft().getSystemIdentifierName() });
}
return tests.iterator();
}
@DataProvider(name = "groovy-only")
public Object[][] createGroovyData() throws Exception {
return new Object[][]{
{"groovy"},
{"groovyremote"}
};
}
@BeforeClass
public void setUp() throws Exception {
// Start OpenICF Connector Server
String openicfServerPort =
IdentityServer.getInstance().getProperty("openicfServerPort", "8759");
int port = 8759;// Integer.getInteger(openicfServerPort);
System.setProperty(Log.LOGSPI_PROP, NoOpLogger.class.getName());
connectorServer = new ConnectorServerImpl();
connectorServer.setPort(port);
File root = new File(OpenICFProvisionerService.class.getResource("/").toURI());
List<URL> bundleURLs = new ArrayList<URL>();
File[] connectors = (new File(root, "/connectors/")).listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
});
assertThat(connectors).isNotNull().overridingErrorMessage("You must copy the connectors first");
for (File connector : connectors) {
bundleURLs.add(connector.toURI().toURL());
}
// No Connectors were found!
assertThat(bundleURLs.isEmpty()).isFalse();
connectorServer.setBundleURLs(bundleURLs);
connectorServer.setKeyHash("xOS4IeeE6eb/AhMbhxZEC37PgtE=");
connectorServer.setIfAddress(InetAddress.getByName("127.0.0.1"));
connectorServer.start();
// Start ConnectorInfoProvider Service
Dictionary<String, Object> properties = new Hashtable<String, Object>(3);
properties.put(JSONEnhancedConfig.JSON_CONFIG_PROPERTY, CONFIGURATION_TEMPLATE);
// mocking
ComponentContext context = mock(ComponentContext.class);
// stubbing
when(context.getProperties()).thenReturn(properties);
provider = Pair.of(new ConnectorInfoProviderService(), context);
provider.getLeft().connectorFrameworkFactory = new ConnectorFrameworkFactory();
provider.getLeft().bindEnhancedConfig(new JSONEnhancedConfig());
provider.getLeft().activate(context);
File[] configJsons = (new File(root, "/config/")).listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith("provisioner.openicf-");
}
});
assertThat(configJsons).isNotNull().overridingErrorMessage("You must copy the configurations first");
for (final File configJson : configJsons) {
// Start OpenICFProvisionerService Service
properties = new Hashtable<String, Object>(3);
// properties.put(ComponentConstants.COMPONENT_ID, 42);
// properties.put(ComponentConstants.COMPONENT_NAME,
// getClass().getCanonicalName());
properties.put(JSONEnhancedConfig.JSON_CONFIG_PROPERTY, FileUtil.readFile(configJson));
context = mock(ComponentContext.class);
// stubbing
when(context.getProperties()).thenReturn(properties);
OpenICFProvisionerService service = new OpenICFProvisionerService();
service.bindConnectorInfoProvider(provider.getLeft());
service.bindRouterRegistry(this);
service.bindSyncFailureHandlerFactory(this);
service.bindEnhancedConfig(new JSONEnhancedConfig());
service.bindConnectionFactory(new IDMConnectionFactoryWrapper(Resources.newInternalConnectionFactory(router)));
//set as NullActivityLogger to be the mock logger.
service.setActivityLogger(NullActivityLogger.INSTANCE);
// Attempt to activate the provisioner service up to 4 times, using ConnectorFacade#test to
// validate proper initialization. If the connector info manager is not be initialized, the
// test fails because the connector cannot connect to the remote server. In this test, it
// manifests as a timing issue owing to the flexibility in the provisioner service and the
// connector info provider supporting the ability for the connector server to come and go, as
// managed by the health check thread (see ConnectorInfoProviderService#initialiseRemoteManager).
// The test simply executes too fast for the health check thread to complete setup of the
// connector info manager.
for (int count = 0; count < 4; count++) {
service.activate(context);
try {
service.getConnectorFacade().test();
break;
} catch (Exception e) {
Thread.sleep(1000);
}
}
systems.add(Pair.of(service, context));
}
// bind SystemObjectSetService dependencies in closure as the bind methods
// are protected
SystemObjectSetService systemObjectSetService =
new SystemObjectSetService() {{
bindConnectionFactory(new IDMConnectionFactoryWrapper(Resources.newInternalConnectionFactory(router)));
for (Pair<OpenICFProvisionerService, ComponentContext> pair : systems) {
bindProvisionerService(pair.getLeft(),(Map) null);
}
}};
router.addRoute(uriTemplate("system"), systemObjectSetService);
connection = Resources.newInternalConnection(router);
}
@AfterClass
public void tearDown() throws Exception {
for (Pair<OpenICFProvisionerService, ComponentContext> pair : systems) {
pair.getLeft().deactivate(pair.getRight());
}
provider.getLeft().deactivate(provider.getRight());
connectorServer.stop();
}
@Test(dataProvider = "dp")
public void testReadInstance(String systemName) throws Exception {
}
@Test(dataProvider = "dp")
public void testActionInstance(String systemName) throws Exception {
}
@Test
public void testPatchInstance() throws Exception {
String name = "john";
String resourceContainer = "/system/XML/account/";
JsonValue object = json(object(
field("name", name),
field("__PASSWORD__", "password"),
field("lastname", "Doe"),
field("email", name + "@example.com"),
field("address", "1234 NE 56th AVE"),
field("age", 30)));
CreateRequest createRequest = Requests.newCreateRequest(resourceContainer, object);
JsonValue createdObject = connection.create(new SecurityContext(new RootContext(), "system", null ), createRequest).getContent();
String resourceName = resourceContainer + createdObject.get("_id").asString();
// Test replace operation
PatchOperation operation = PatchOperation.replace("lastname", "Doe2");
PatchRequest patchRequest = Requests.newPatchRequest(resourceName, operation);
JsonValue patchResult = connection.patch(new RootContext(), patchRequest).getContent();
assertThat(patchResult.get("lastname").asString()).isEqualTo("Doe2");
// Test increment operation
operation = PatchOperation.increment("age", 10);
patchRequest = Requests.newPatchRequest(resourceName, operation);
patchResult = connection.patch(new RootContext(), patchRequest).getContent();
assertThat(patchResult.get("age").asInteger()).isEqualTo(40);
// Test remove operation with no value provided
operation = PatchOperation.remove("age");
patchRequest = Requests.newPatchRequest(resourceName, operation);
patchResult = connection.patch(new RootContext(), patchRequest).getContent();
assertThat(patchResult.get("age").isNull()).isEqualTo(true);
// Test remove operation with value provided that is wrong
operation = PatchOperation.remove("address", "1234");
patchRequest = Requests.newPatchRequest(resourceName, operation);
patchResult = connection.patch(new RootContext(), patchRequest).getContent();
assertThat(patchResult.get("address").get(0).getObject()).isEqualTo("1234 NE 56th AVE");
// Test remove operation with value provided
operation = PatchOperation.remove("address", "1234 NE 56th AVE");
patchRequest = Requests.newPatchRequest(resourceName, operation);
patchResult = connection.patch(new RootContext(), patchRequest).getContent();
assertThat(patchResult.get("address").isNull()).isEqualTo(true);
// Test add operation
operation = PatchOperation.add("gender", "m");
patchRequest = Requests.newPatchRequest(resourceName, operation);
patchResult = connection.patch(new RootContext(), patchRequest).getContent();
assertThat(patchResult.get("gender").asString()).isEqualTo("m");
// clean up by deleting this user
connection.delete(new SecurityContext(new RootContext(), "system", null),
Requests.newDeleteRequest(resourceContainer, patchResult.get("__UID__").asString()));
}
@Test
public void testPatchAddOnArray() throws Exception {
String name = "jane";
String resourceContainer = "/system/XML/account/";
JsonValue object = json(object(
field("name", name),
field("__PASSWORD__", "password"),
field("lastname", "smith"),
field("email", name + "@example.com"),
field("age", 29)));
CreateRequest createRequest = Requests.newCreateRequest(resourceContainer, object);
JsonValue createdObject = connection.create(
new SecurityContext(new RootContext(), "system", null), createRequest).getContent();
String resourceName = resourceContainer + createdObject.get("_id").asString();
// Add another email into the array
PatchOperation operation = PatchOperation.add("/email/-", name + "@example2.com");
PatchRequest patchRequest = Requests.newPatchRequest(resourceName, operation);
JsonValue patchResult = connection.patch(new RootContext(), patchRequest).getContent();
assertThat(patchResult.get(new JsonPointer("/email")).asList().size()).isEqualTo(2);
assertThat(patchResult.get(new JsonPointer("/email/1")).asString()).isEqualTo(name + "@example2.com");
// clean up by deleting this user
connection.delete(new SecurityContext(new RootContext(), "system", null),
Requests.newDeleteRequest(resourceContainer, patchResult.get("__UID__").asString()));
}
@Test
public void testPatchRemoveOnArray() throws Exception {
String name = "jane";
String resourceContainer = "/system/XML/account/";
JsonValue object = json(object(
field("name", name),
field("__PASSWORD__", "password"),
field("lastname", "smith"),
field("email", name + "@example.com"),
field("age", 29)));
CreateRequest createRequest = Requests.newCreateRequest(resourceContainer, object);
JsonValue createdObject = connection.create(
new SecurityContext(new RootContext(), "system", null), createRequest).getContent();
String resourceName = resourceContainer + createdObject.get("_id").asString();
// Add another email into the array
PatchOperation addOperation = PatchOperation.add("/email/-",
Arrays.asList(name + "@example2.com", name +"@example3.com"));
PatchRequest patchRequest = Requests.newPatchRequest(resourceName, addOperation);
JsonValue patchResult = connection.patch(new RootContext(), patchRequest).getContent();
assertThat(patchResult.get(new JsonPointer("/email")).asList().size()).isEqualTo(3);
assertThat(patchResult.get(new JsonPointer("/email/1")).asString()).isEqualTo(name + "@example2.com");
// remove a value that doesn't exist in existing value from the array
PatchOperation removeOperation = PatchOperation.remove("/email", name + "@example4.com");
patchRequest = Requests.newPatchRequest(resourceName, removeOperation);
patchResult = connection.patch(new RootContext(), patchRequest).getContent();
// assert that the value was not changed
assertThat(patchResult.get(new JsonPointer("/email")).asList().size()).isEqualTo(3);
// removing from array giving explicit value,
removeOperation = PatchOperation.remove("/email", name + "@example2.com");
patchRequest = Requests.newPatchRequest(resourceName, removeOperation);
patchResult = connection.patch(new RootContext(), patchRequest).getContent();
assertThat(patchResult.get(new JsonPointer("/email")).asList().size()).isEqualTo(2);
// remove using json pointer with no value
removeOperation = PatchOperation.remove("/email/1");
patchRequest = Requests.newPatchRequest(resourceName, removeOperation);
patchResult = connection.patch(new RootContext(), patchRequest).getContent();
assertThat(patchResult.get(new JsonPointer("/email")).asList().size()).isEqualTo(1);
// clean up by deleting this user
connection.delete(new SecurityContext(new RootContext(), "system", null),
Requests.newDeleteRequest(resourceContainer, patchResult.get("__UID__").asString()));
}
// Test remove attribute that doesn't exist in the target system
@Test(expectedExceptions = BadRequestException.class)
public void testRemoveUnsupportedAttribute() throws Exception {
String name = "jane";
String resourceContainer = "/system/XML/account/";
JsonValue object = json(object(
field("name", name),
field("__PASSWORD__", "password"),
field("lastname", "smith"),
field("email", name + "@example.com"),
field("age", 29)));
CreateRequest createRequest = Requests.newCreateRequest(resourceContainer, object);
JsonValue createdObject = connection.create(
new SecurityContext(new RootContext(), "system", null), createRequest).getContent();
String resourceName = resourceContainer + createdObject.get("_id").asString();
PatchOperation removeOperation = PatchOperation.remove("/unsupportedAttribute");
PatchRequest patchRequest = Requests.newPatchRequest(resourceName, removeOperation);
try {
connection.patch(new RootContext(), patchRequest).getContent();
} finally {
// clean up by deleting this user
connection.delete(new SecurityContext(new RootContext(), "system", null),
Requests.newDeleteRequest(resourceContainer, createdObject.get("__UID__").asString()));
}
}
// Test to make sure that value types can't mismatch
@Test(expectedExceptions = InternalServerErrorException.class)
public void testAttributeTypeValueMismatch() throws Exception {
String name = "jane";
String resourceContainer = "/system/XML/account/";
JsonValue object = json(object(
field("name", name),
field("__PASSWORD__", "password"),
field("lastname", "smith"),
field("email", name + "@example.com"),
field("age", 29)));
CreateRequest createRequest = Requests.newCreateRequest(resourceContainer, object);
JsonValue createdObject = connection.create(
new SecurityContext(new RootContext(), "system", null), createRequest).getContent();
String resourceName = resourceContainer + createdObject.get("_id").asString();
PatchOperation removeOperation = PatchOperation.replace("age", "twenty-nine");
PatchRequest patchRequest = Requests.newPatchRequest(resourceName, removeOperation);
try {
connection.patch(new RootContext(), patchRequest).getContent();
} finally {
// clean up by deleting this user
connection.delete(new SecurityContext(new RootContext(), "system", null),
Requests.newDeleteRequest(resourceContainer, createdObject.get("__UID__").asString()));
}
}
@Test(expectedExceptions = InternalServerErrorException.class)
public void testRemoveRequiredAttribute() throws Exception {
String name = "jane";
String resourceContainer = "/system/XML/account/";
JsonValue object = json(object(
field("name", name),
field("__PASSWORD__", "password"),
field("lastname", "smith"),
field("email", name + "@example.com"),
field("age", 29)));
CreateRequest createRequest = Requests.newCreateRequest(resourceContainer, object);
JsonValue createdObject = connection.create(
new SecurityContext(new RootContext(), "system", null), createRequest).getContent();
String resourceName = resourceContainer + createdObject.get("_id").asString();
// Try to remove the lastname that is required
PatchOperation operation = PatchOperation.remove("lastname", "smith");
PatchRequest patchRequest = Requests.newPatchRequest(resourceName, operation);
try {
connection.patch(new RootContext(), patchRequest).getContent();
} finally {
// clean up by deleting this user
connection.delete(new SecurityContext(new RootContext(), "system", null),
Requests.newDeleteRequest(resourceContainer, createdObject.get("__UID__").asString()));
}
}
@Test(dataProvider = "dp")
public void testUpdateInstance(String systemName) throws Exception {
}
private JsonValue getTestConnectorObject(String name) {
JsonValue createAttributes = new JsonValue(new LinkedHashMap<String, Object>());
createAttributes.put(Name.NAME, name);
createAttributes.put("attributeString", name);
createAttributes.put("attributeLong", (long) name.hashCode());
return createAttributes;
}
private JsonValue getAccountObject(String name) {
JsonValue createAttributes = new JsonValue(new LinkedHashMap<String, Object>());
createAttributes.put(Name.NAME, name);
createAttributes.put("userName", name);
createAttributes.put("email", name + "@example.com");
return createAttributes;
}
private static class SyncStub implements SingletonResourceProvider {
final public ArrayList<ActionRequest> requests = new ArrayList<ActionRequest>();
public Promise<ActionResponse, ResourceException> actionInstance(Context context, ActionRequest request) {
requests.add(request);
return newActionResponse(new JsonValue(true)).asPromise();
}
public Promise<ResourceResponse, ResourceException> patchInstance(Context context, PatchRequest request) {
return new NotSupportedException().asPromise();
}
public Promise<ResourceResponse, ResourceException> readInstance(Context context, ReadRequest request) {
return new NotSupportedException().asPromise();
}
public Promise<ResourceResponse, ResourceException> updateInstance(Context context, UpdateRequest request) {
return new NotSupportedException().asPromise();
}
}
@Test(dataProvider = "groovy-only", enabled = true)
public void testSync(String systemName) throws Exception {
JsonValue stage = new JsonValue(new LinkedHashMap<String, Object>());
stage.put("connectorData", ConnectorUtil.convertFromSyncToken(new SyncToken(0)));
CreateRequest createRequest = Requests
.newCreateRequest("repo/synchronisation/pooledSyncStage",
("system" + systemName + "account").toUpperCase(),
stage);
connection.create(new RootContext(), createRequest);
SyncStub sync = new SyncStub();
RouteMatcher r = router.addRoute(uriTemplate("sync"), sync);
ActionRequest actionRequest = Requests.newActionRequest("system/" + systemName + "/account",
SystemObjectSetService.SystemAction.liveSync.toString());
ActionResponse response = connection.action(new RootContext(), actionRequest);
assertThat(ConnectorUtil.convertToSyncToken(
response.getJsonContent().get("connectorData")).getValue()).isEqualTo(1);
assertThat(sync.requests.size()).isEqualTo(1);
ActionRequest delta = sync.requests.remove(0);
assertThat(delta.getAction()).isEqualTo("notifyCreate");
response = connection.action(new RootContext(), actionRequest);
assertThat(ConnectorUtil.convertToSyncToken(
response.getJsonContent().get("connectorData")).getValue()).isEqualTo(2);
assertThat(sync.requests.size()).isEqualTo( 1);
delta = sync.requests.remove(0);
assertThat(delta.getAction()).isEqualTo("notifyUpdate");
response = connection.action(new RootContext(), actionRequest);
assertThat(ConnectorUtil.convertToSyncToken(
response.getJsonContent().get("connectorData")).getValue()).isEqualTo(3);
assertThat(sync.requests.size()).isEqualTo( 1);
delta = sync.requests.remove(0);
assertThat(delta.getAction()).isEqualTo("notifyUpdate");
response = connection.action(new RootContext(), actionRequest);
assertThat(ConnectorUtil.convertToSyncToken(
response.getJsonContent().get("connectorData")).getValue()).isEqualTo(4);
assertThat(sync.requests.size()).isEqualTo(1);
delta = sync.requests.remove(0);
assertThat(delta.getAction()).isEqualTo("notifyUpdate");
assertThat(delta.getContent().get("newValue").get("_previous-id").asString()).isEqualTo("001");
response = connection.action(new RootContext(), actionRequest);
assertThat(ConnectorUtil.convertToSyncToken(
response.getJsonContent().get("connectorData")).getValue()).isEqualTo(5);
assertThat(sync.requests.size()).isEqualTo(1);
delta = sync.requests.remove(0);
assertThat(delta.getAction()).isEqualTo("notifyDelete");
response = connection.action(new RootContext(), actionRequest);
assertThat(ConnectorUtil.convertToSyncToken(
response.getJsonContent().get("connectorData")).getValue()).isEqualTo(10);
assertThat(sync.requests.isEmpty()).isTrue();
response = connection.action(new RootContext(), actionRequest);
assertThat(ConnectorUtil.convertToSyncToken(
response.getJsonContent().get("connectorData")).getValue()).isEqualTo(17);
assertThat(sync.requests.size()).isEqualTo(4);
sync.requests.clear();
stage = new JsonValue(new LinkedHashMap<String, Object>());
stage.put("connectorData", ConnectorUtil.convertFromSyncToken(new SyncToken(10)));
createRequest = Requests
.newCreateRequest("repo/synchronisation/pooledSyncStage",
("system" + systemName + "group").toUpperCase(),
stage);
connection.create(new RootContext(), createRequest);
actionRequest = Requests.newActionRequest("system/" + systemName + "/group",
SystemObjectSetService.SystemAction.liveSync.toString());
response = connection.action(new RootContext(), actionRequest);
assertThat(ConnectorUtil.convertToSyncToken(
response.getJsonContent().get("connectorData")).getValue()).isEqualTo(16);
assertThat(sync.requests.size()).isEqualTo(3);
router.removeRoute(r);
}
@Test(dataProvider = "groovy-only", enabled = false)
public void testPagedSearch(String systemName) throws Exception {
for (int i = 0; i < 100; i++) {
JsonValue co = getAccountObject(String.format("TEST%05d", i));
co.put("sortKey", i);
CreateRequest request = Requests.newCreateRequest("system/" + systemName + "/account", co);
connection.create(new SecurityContext(new RootContext(), "system", null ), request);
}
QueryRequest queryRequest = Requests.newQueryRequest("system/" + systemName + "/account");
queryRequest.setPageSize(10);
queryRequest.addSortKey(SortKey.descendingOrder("sortKey"));
queryRequest.setQueryFilter(QueryFilter.<JsonPointer>startsWith(new JsonPointer("__NAME__"), "TEST"));
QueryResponse result = null;
final Set<ResourceResponse> resultSet = new HashSet<ResourceResponse>();
int pageIndex = 0;
try {
while ((result = connection.query(new RootContext(), queryRequest, new QueryResourceHandler() {
private int index = 101;
public boolean handleResource(ResourceResponse resource) {
Integer idx = resource.getContent().get("sortKey").asInteger();
assertThat(idx < index).isTrue();
index = idx;
return resultSet.add(resource);
}
})).getPagedResultsCookie() != null) {
queryRequest.setPagedResultsCookie(result.getPagedResultsCookie());
assertThat(resultSet.size()).isEqualTo(10 * ++pageIndex);
}
} catch (ResourceException e) {
fail(e.getMessage());
}
assertThat(pageIndex).isEqualTo(9);
assertThat(resultSet.size()).isEqualTo(100);
}
@Test(dataProvider = "dp", enabled = true)
public void testHelloWorldAction(String systemName) throws Exception {
if ("Test".equals(systemName)) {
//Request#1
ActionRequest actionRequest = Requests.newActionRequest("/system/Test", "script");
actionRequest.setAdditionalParameter(SystemAction.SCRIPT_ID, "ConnectorScript#1");
ActionResponse result = connection.action(new RootContext(), actionRequest);
assertThat(result.getJsonContent().get(new JsonPointer("actions/0/result")).getObject()).isEqualTo(
"Arthur Dent");
//Request#2
actionRequest = Requests.newActionRequest("/system/Test", "script");
actionRequest.setAdditionalParameter(SystemAction.SCRIPT_ID, "ConnectorScript#2");
JsonValue content = new JsonValue(new HashMap<String, Object>());
content.put("testArgument", "Zaphod Beeblebrox");
actionRequest.setContent(content);
result = connection.action(new RootContext(), actionRequest);
assertThat(result.getJsonContent().get(new JsonPointer("actions/0/result")).getObject()).isEqualTo(
"Zaphod Beeblebrox");
//Request#3
actionRequest = Requests.newActionRequest("/system/Test", "script");
actionRequest.setAdditionalParameter(SystemAction.SCRIPT_ID, "ConnectorScript#3");
content = new JsonValue(new HashMap<String, Object>());
content.put("testArgument", Arrays.asList("Ford Prefect", "Tricia McMillan"));
actionRequest.setContent(content);
result = connection.action(new RootContext(), actionRequest);
assertThat(result.getJsonContent().get(new JsonPointer("actions/0/result")).getObject()).isEqualTo(2);
//Request#4
actionRequest = Requests.newActionRequest("/system/Test", "script");
actionRequest.setAdditionalParameter(SystemAction.SCRIPT_ID, "ConnectorScript#4");
result = connection.action(new RootContext(), actionRequest);
assertThat(result.getJsonContent().get(new JsonPointer("actions/0/error")).getObject()).isEqualTo(
"Marvin");
}
}
// AlreadyExistsException -> PreconditionFailedException
@Test(dataProvider = "groovy-only", expectedExceptions = PreconditionFailedException.class, enabled = true)
public void testConflictException(String systemName) throws Exception {
CreateRequest createRequest = Requests.newCreateRequest("system/" + systemName + "/__TEST__", getTestConnectorObject("TEST1"));
connection.create(new SecurityContext(new RootContext(), "system", null ), createRequest);
}
// ConnectorIOException -> ServiceUnavailableException - Will work when new groovy script is updated
@Test(dataProvider = "groovy-only", expectedExceptions = ServiceUnavailableException.class , enabled = true)
public void testServiceUnavailableExceptionFromConnectorIOException(String systemName) throws Exception {
DeleteRequest deleteRequest = Requests.newDeleteRequest("system/" + systemName + "/__TEST__/TESTEX_CIO");
connection.delete((new SecurityContext(new RootContext(), "system", null)), deleteRequest);
}
// OperationTimeoutException -> ServiceUnavailableException
@Test(dataProvider = "groovy-only", expectedExceptions = ServiceUnavailableException.class , enabled = true)
public void testServiceUnavailableExceptionFromOperationTimeoutException(String systemName) throws Exception {
DeleteRequest deleteRequest = Requests.newDeleteRequest("system/" + systemName + "/__TEST__/TESTEX_OT");
connection.delete((new SecurityContext(new RootContext(), "system", null )), deleteRequest);
}
// RetryableException -> ServiceUnavailableException
@Test(dataProvider = "groovy-only", expectedExceptions = ServiceUnavailableException.class , enabled = true)
public void testServiceUnavailableExceptionFromRetryableException(String systemName) throws Exception {
CreateRequest createRequest = Requests.newCreateRequest("system/" + systemName + "/__TEST__", getTestConnectorObject("TEST4"));
connection.create(new SecurityContext(new RootContext(), "system", null), createRequest);
}
// ConfigurationException -> InternalServerErrorException
@Test(dataProvider = "groovy-only", expectedExceptions = InternalServerErrorException.class, enabled = true)
public void testInternalServerErrorExceptionFromConfigurationException(String systemName) throws Exception {
DeleteRequest deleteRequest = Requests.newDeleteRequest("system/" + systemName + "/__TEST__/TESTEX_CE");
connection.delete(new RootContext(), deleteRequest);
}
// ConnectionBrokenException -> ServiceUnavailableException
@Test(dataProvider = "groovy-only", expectedExceptions = ServiceUnavailableException.class, enabled = true)
public void testServiceUnavailableExceptionFromConnectionBrokenException(String systemName) throws Exception {
DeleteRequest deleteRequest = Requests.newDeleteRequest("system/" + systemName + "/__TEST__/TESTEX_CB");
connection.delete(new RootContext(), deleteRequest);
}
// ConnectionFailedException -> ServiceUnavailableException
@Test(dataProvider = "groovy-only", expectedExceptions = ServiceUnavailableException.class, enabled = true)
public void testServiceUnavailableExceptionFromConnectionFailedException(String systemName) throws Exception {
DeleteRequest deleteRequest = Requests.newDeleteRequest("system/" + systemName + "/__TEST__/TESTEX_CF");
connection.delete(new RootContext(), deleteRequest);
}
// ConnectorException -> InternalServerErrorException
@Test(dataProvider = "groovy-only", expectedExceptions = InternalServerErrorException.class, enabled = true)
public void testInternalServerErrorExceptionFromConnectorException(String systemName) throws Exception {
DeleteRequest deleteRequest = Requests.newDeleteRequest("system/" + systemName + "/__TEST__/TESTEX_C");
connection.delete(new RootContext(), deleteRequest);
}
// NullPointerException -> InternalServerErrorException
@Test(dataProvider = "groovy-only", expectedExceptions = InternalServerErrorException.class, enabled = true)
public void testInternalServerErrorExceptionFromNullPointerException(String systemName) throws Exception {
DeleteRequest deleteRequest = Requests.newDeleteRequest("system/" + systemName + "/__TEST__/TESTEX_NPE");
connection.delete(new RootContext(), deleteRequest);
}
// IllegalArgumentException -> InternalServerErrorException
@Test(dataProvider = "groovy-only", expectedExceptions = InternalServerErrorException.class, enabled = true)
public void testInternalServerErrorExceptionFromIllegalArgumentException(String systemName) throws Exception {
CreateRequest createRequest = Requests.newCreateRequest("system/" + systemName + "/__TEST__", getTestConnectorObject("TEST3"));
connection.create(new SecurityContext(new RootContext(), "system", null), createRequest);
}
// ConnectorSecurityException -> InternalServerErrorException
@Test(dataProvider = "groovy-only", expectedExceptions = InternalServerErrorException.class, enabled = true)
public void testInternalServerErrorExceptionFromConnectorSecurityException(String systemName) throws Exception {
ActionRequest actionRequest = Requests.newActionRequest("system/" + systemName + "/__TEST__", "authenticate");
actionRequest.setAdditionalParameter("username", "TEST1");
actionRequest.setAdditionalParameter("password", "Passw0rd");
connection.action(new RootContext(), actionRequest);
}
// InvalidCredentialException - > PermanentException (UNAUTHORIZED_ERROR_CODE)
@Test(dataProvider = "groovy-only", expectedExceptions = PermanentException.class, enabled = true)
public void testPermanentExceptionFromInvalidCredentialException(String systemName) throws Exception {
ActionRequest actionRequest = Requests.newActionRequest("system/" + systemName + "/__TEST__", "authenticate");
actionRequest.setAdditionalParameter("username", "TEST2");
actionRequest.setAdditionalParameter("password", "Passw0rd");
connection.action(new RootContext(), actionRequest);
}
// InvalidPasswordException -> PermanentException (UNAUTHORIZED_ERROR_CODE)
@Test(dataProvider = "groovy-only", expectedExceptions = PermanentException.class, enabled = true)
public void testPermanentExceptionFromInvalidPasswordException(String systemName) throws Exception {
ActionRequest actionRequest = Requests.newActionRequest("system/" + systemName + "/__TEST__", "authenticate");
actionRequest.setAdditionalParameter("username", "TEST3");
actionRequest.setAdditionalParameter("password", "Passw0rd");
connection.action(new RootContext(), actionRequest);
}
// PermissionDeniedException -> ForbiddenException
@Test(dataProvider = "groovy-only", expectedExceptions = ForbiddenException.class, enabled = true)
public void testForbiddenExceptionPermissionDeniedException(String systemName) throws Exception {
ActionRequest actionRequest = Requests.newActionRequest("system/" + systemName + "/__TEST__", "authenticate");
actionRequest.setAdditionalParameter("username", "TEST4");
actionRequest.setAdditionalParameter("password", "Passw0rd");
connection.action(new RootContext(), actionRequest);
}
// PasswordExpiredException -> ForbiddenException
@Test(dataProvider = "groovy-only", expectedExceptions = ForbiddenException.class, enabled = true)
public void testForbiddenExceptionFromPasswordExpiredException(String systemName) throws Exception {
ActionRequest actionRequest = Requests.newActionRequest("system/" + systemName + "/__TEST__", "authenticate");
actionRequest.setAdditionalParameter("username", "TEST5");
actionRequest.setAdditionalParameter("password", "Passw0rd");
connection.action(new RootContext(), actionRequest);
}
// UnknownUidException -> NotFoundException
@Test(dataProvider = "groovy-only", expectedExceptions = NotFoundException.class, enabled = true)
public void testNotFoundExceptionFromUnknownException(String systemName) throws Exception {
ActionRequest actionRequest = Requests.newActionRequest("system/" + systemName + "/__SAMPLE__", "authenticate");
actionRequest.setAdditionalParameter("username", "Unknown-UID");
actionRequest.setAdditionalParameter("password", "Passw0rd");
connection.action(new RootContext(), actionRequest);
}
// UnsupportedOperationException -> NotFoundException
@Test(dataProvider = "groovy-only", expectedExceptions = NotFoundException.class, enabled = true)
public void testNotFoundExceptionFromUnsupportedOperationException(String systemName) throws Exception {
ActionRequest actionRequest = Requests.newActionRequest("system/" + systemName + "/Unsupported-Object", "authenticate");
actionRequest.setAdditionalParameter("username", "TEST6");
actionRequest.setAdditionalParameter("password", "Passw0rd");
connection.action(new RootContext(), actionRequest);
}
// InvalidAttributeValueException - > BadRequestException
@Test(dataProvider = "groovy-only", expectedExceptions = BadRequestException.class, enabled = true)
public void testBadRequestException(String systemName) throws Exception {
CreateRequest createRequest = Requests.newCreateRequest("system/" + systemName + "/__TEST__", getTestConnectorObject("TEST2"));
connection.create(new SecurityContext(new RootContext(), "system", null), createRequest);
}
// PreconditionFailedException -> org.forgerock.json.resource.PreconditionFailedException
@Test(dataProvider = "groovy-only", expectedExceptions = PreconditionFailedException.class, enabled = true)
public void testPreconditionFailedException(String systemName) throws Exception {
final String resourceId = "TEST4";
UpdateRequest updateRequest = Requests.newUpdateRequest("system/" + systemName + "/__TEST__/",
resourceId,
getTestConnectorObject(resourceId));
connection.update(new SecurityContext(new RootContext(), "system", null), updateRequest);
}
// PreconditionRequiredException -> org.forgerock.json.resource.PreconditionRequiredException
@Test(dataProvider = "groovy-only", expectedExceptions = PreconditionRequiredException.class, enabled = true)
public void testPreconditionRequiredException(String systemName) throws Exception {
final String resourceId = "TEST5";
UpdateRequest updateRequest = Requests.newUpdateRequest("system/" + systemName + "/__TEST__/",
resourceId,
getTestConnectorObject(resourceId));
connection.update(new SecurityContext(new RootContext(), "system", null ), updateRequest);
}
// ResourceException -> org.forgerock.json.resource.ResourceException
@Test(dataProvider = "groovy-only", expectedExceptions = ResourceException.class, enabled = true)
public void testResourceException(String systemName) throws Exception {
final String resourceId = "TEST6";
JsonValue user = getTestConnectorObject(resourceId);
user.put("missingKey", "ignoredValue");
UpdateRequest updateRequest = Requests.newUpdateRequest("system/" + systemName + "/__TEST__/",
resourceId,
user);
connection.update(new SecurityContext(new RootContext(), "system", null ), updateRequest);
}
@Test(dataProvider = "groovy-only", enabled = true)
public void testSyncWithAllObjectClass(String systemName) throws Exception {
JsonValue stage = new JsonValue(new LinkedHashMap<String, Object>());
stage.put("connectorData", ConnectorUtil.convertFromSyncToken(new SyncToken(17)));
CreateRequest createRequest = Requests
.newCreateRequest("repo/synchronisation/pooledSyncStage",
("system" + systemName).toUpperCase(),
stage);
connection.create(new RootContext(), createRequest);
SyncStub sync = new SyncStub();
RouteMatcher r = router.addRoute(uriTemplate("sync"), sync);
ActionRequest actionRequest = Requests.newActionRequest("system/" + systemName,
SystemObjectSetService.SystemAction.liveSync.toString());
ActionResponse response = connection.action(new RootContext(), actionRequest);
assertThat(ConnectorUtil.convertToSyncToken(
response.getJsonContent().get("connectorData")).getValue()).isEqualTo(17);
assertThat(sync.requests.size()).isEqualTo(0);
router.removeRoute(r);
}
@Override
public SyncFailureHandler create(JsonValue config) throws Exception {
return NullSyncFailureHandler.INSTANCE;
}
}