/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2014-2015 ForgeRock AS. All Rights Reserved
*
* 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
* http://forgerock.org/license/CDDLv1.0.html
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at http://forgerock.org/license/CDDLv1.0.html
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*/
package org.forgerock.openidm.config.manage;
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.openidm.config.manage.ConfigObjectService.asConfigQueryFilter;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import org.forgerock.openidm.router.IDMConnectionFactory;
import org.forgerock.services.context.Context;
import org.forgerock.json.resource.ResourcePath;
import org.forgerock.json.JsonPointer;
import org.forgerock.json.JsonValue;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.Connection;
import org.forgerock.json.resource.NotFoundException;
import org.forgerock.json.resource.PreconditionFailedException;
import org.forgerock.json.resource.QueryFilters;
import org.forgerock.json.resource.QueryRequest;
import org.forgerock.json.resource.QueryResourceHandler;
import org.forgerock.json.resource.ResourceResponse;
import org.forgerock.openidm.config.crypto.ConfigCrypto;
import org.forgerock.openidm.config.enhanced.EnhancedConfig;
import org.forgerock.openidm.config.enhanced.JSONEnhancedConfig;
import org.forgerock.openidm.core.ServerConstants;
import org.forgerock.openidm.metadata.impl.ProviderListener;
import org.forgerock.util.query.QueryFilter;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.component.ComponentContext;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
/**
* Test class for {@link ConfigObjectService}
*/
public class ConfigObjectServiceTest {
@SuppressWarnings("rawtypes")
private Dictionary properties = null;
private ConfigObjectService configObjectService;
private ResourcePath rname;
private String id;
private Map<String,Object> config;
private ConfigurationAdmin configAdmin;
private EnhancedConfig enhancedConfig;
@SuppressWarnings("unchecked")
@BeforeTest
public void beforeTest() throws Exception {
properties = new Hashtable<>();
properties.put(ComponentConstants.COMPONENT_NAME, getClass().getName());
// Set root
URL root = ConfigObjectServiceTest.class.getResource("/");
assertNotNull(root);
String rootPath = URLDecoder.decode(root.getPath(), "UTF-8");
System.setProperty(ServerConstants.PROPERTY_SERVER_ROOT, rootPath);
// Mock up supporting objects and activate ConfigObjectService
ComponentContext context = mock(ComponentContext.class);
when(context.getProperties()).thenReturn(properties);
configObjectService = new ConfigObjectService();
configAdmin = new MockConfigurationAdmin();
// no accessible bindConfigurationAdmin() method
Field field = ConfigObjectService.class.getDeclaredField("configAdmin");
field.setAccessible(true);
field.set(configObjectService, configAdmin);
BundleContext bundleContext = mock(BundleContext.class);
when(bundleContext.getBundles()).thenReturn(new Bundle[0]);
// Init the ConfigCrypto instance used by ConfigObjectService
ConfigCrypto.getInstance(bundleContext, mock(ProviderListener.class));
IDMConnectionFactory connectionFactory = mock(IDMConnectionFactory.class);
when(connectionFactory.getConnection()).thenReturn(mock(Connection.class));
enhancedConfig = mock(EnhancedConfig.class);
configObjectService.bindEnhancedConfig(enhancedConfig);
configObjectService.bindConnectionFactory(connectionFactory);
configObjectService.activate(context);
rname = new ResourcePath("testobject");
id = "testid";
config = new HashMap<String,Object>();
}
@AfterTest
public void afterTest() throws Exception {
ComponentContext context = mock(ComponentContext.class);
when(context.getProperties()).thenReturn(properties);
configObjectService.deactivate(context);
configObjectService = null;
enhancedConfig = null;
}
@Test(priority=1)
public void testVisitor() throws Exception {
// Config fields
String configField = "/field1";
String nonConfigField = "/service__pid";
// QueryFilter Strings
String queryString1 = configField + " eq \"value1\"";
String queryString2 = "" + queryString1 + " and " + nonConfigField + " eq \"value2\"";
String queryString3 = configField + " pr";
String queryString4 = configField + " lt 1";
String queryString5 = "true";
// QueryFilters
QueryFilter<JsonPointer> filter1 = QueryFilters.parse(queryString1);
QueryFilter<JsonPointer> filter2 = QueryFilters.parse(queryString2);
QueryFilter<JsonPointer> filter3 = QueryFilters.parse(queryString3);
QueryFilter<JsonPointer> filter4 = QueryFilters.parse(queryString4);
QueryFilter<JsonPointer> filter5 = QueryFilters.parse(queryString5);
// Assertions
Assert.assertEquals(asConfigQueryFilter(filter1).toString(), "/jsonconfig/field1 eq \"value1\"");
Assert.assertEquals(asConfigQueryFilter(filter2).toString(), "(/jsonconfig/field1 eq \"value1\" and /service__pid eq \"value2\")");
Assert.assertEquals(asConfigQueryFilter(filter3).toString(), "/jsonconfig/field1 pr");
Assert.assertEquals(asConfigQueryFilter(filter4).toString(), "/jsonconfig/field1 lt 1");
Assert.assertEquals(asConfigQueryFilter(filter5).toString(), "true");
}
@Test(priority=2)
public void testParsedResourceName() throws Exception {
try {
configObjectService.getParsedId("");
Assert.fail("Invalid id: ''");
} catch (BadRequestException e) {
}
try {
configObjectService.getParsedId("//");
Assert.fail("Invalid id: '//'");
} catch (IllegalArgumentException e) {
}
try {
configObjectService.getParsedId("a/b/c");
Assert.fail("Invalid id: 'a/b/c'");
} catch (BadRequestException e) {
}
Assert.assertEquals(configObjectService.getParsedId("/a"), "a");
Assert.assertFalse(configObjectService.isFactoryConfig("/a"));
Assert.assertEquals(configObjectService.getParsedId("a"), "a");
Assert.assertFalse(configObjectService.isFactoryConfig("a"));
Assert.assertEquals(configObjectService.getParsedId("b/"), "b");
Assert.assertFalse(configObjectService.isFactoryConfig("b/"));
Assert.assertEquals(configObjectService.getParsedId("c/d"), "c-d");
assertTrue(configObjectService.isFactoryConfig("c/d"));
Assert.assertEquals(configObjectService.getParsedId("e/d/"), "e-d");
assertTrue(configObjectService.isFactoryConfig("e/d/"));
Assert.assertEquals(configObjectService.getParsedId(" f "), "_f_");
Assert.assertFalse(configObjectService.isFactoryConfig(" f "));
}
@SuppressWarnings("rawtypes")
@Test(priority=3)
public void testCreateNew() throws Exception {
config.put("property1", "value1");
config.put("property2", "value2");
configObjectService.create(rname, id, json(config), false).getOrThrow();
ConfigObjectService.ParsedId parsedId = configObjectService.getParsedId(rname, id);
Configuration config = configObjectService.findExistingConfiguration(parsedId);
assertNotNull(config);
assertNotNull(config.getProperties());
Dictionary properties = config.getProperties();
EnhancedConfig enhancedConfig = new JSONEnhancedConfig();
JsonValue value = enhancedConfig.getConfiguration(properties, rname.toString(), false);
assertTrue(value.keys().contains("property1"));
Assert.assertEquals(value.get("property1").asString(), "value1");
}
@Test(priority=4, expectedExceptions = PreconditionFailedException.class)
public void testCreateDupeFail() throws Exception {
configObjectService.create(rname, id, json(config), false).getOrThrow();
throw new Exception("Duplicate object not detected");
}
@Test(priority=5)
public void testCreateDupeOk() throws Exception {
when(enhancedConfig.getConfiguration(any(Dictionary.class), any(String.class), eq(false)))
.thenReturn(json(config));
configObjectService.create(rname, id, json(config), true).getOrThrow();
ConfigObjectService.ParsedId parsedId = configObjectService.getParsedId(rname, id);
Configuration config = configObjectService.findExistingConfiguration(parsedId);
assertNotNull(config);
assertNotNull(config.getProperties());
}
@SuppressWarnings("rawtypes")
@Test(priority=6)
public void testUpdate() throws Exception {
config.put("property1", "newvalue1");
config.put("property2", "newvalue2");
when(enhancedConfig.getConfiguration(any(Dictionary.class), any(String.class), eq(false)))
.thenReturn(json(config));
configObjectService.update(rname, id, json(config)).getOrThrow();
ConfigObjectService.ParsedId parsedId = configObjectService.getParsedId(rname, id);
Configuration config = configObjectService.findExistingConfiguration(parsedId);
assertNotNull(config);
assertNotNull(config.getProperties());
Dictionary properties = config.getProperties();
JSONEnhancedConfig enhancedConfig = new JSONEnhancedConfig();
JsonValue value = enhancedConfig.getConfiguration(properties, rname.toString(), false);
assertTrue(value.keys().contains("property1"));
Assert.assertEquals(value.get("property1").asString(), "newvalue1");
}
@Test(priority=7)
public void testQuery() throws Exception {
configObjectService.handleQuery(mock(Context.class),
mock(QueryRequest.class), mock(QueryResourceHandler.class)).getOrThrow();
}
@Test(priority=8)
public void testDelete() throws Exception {
when(enhancedConfig.getConfiguration(any(Dictionary.class), any(String.class), eq(false))).thenReturn(
json(object(field(ResourceResponse.FIELD_CONTENT_REVISION, "revX"))));
configObjectService.delete(rname, "0").getOrThrow();
ConfigObjectService.ParsedId parsedId = configObjectService.getParsedId(rname, id);
Configuration config = configObjectService.findExistingConfiguration(parsedId);
// "deleting" the object does not remove it from the configAdmin but it does invalidate the config
assertNotNull(config);
assertNull(config.getProperties());
}
@Test(priority=9, expectedExceptions = NotFoundException.class)
public void testUpdateFail() throws Exception {
config.put("property1", "newnewvalue1");
config.put("property2", "newnewvalue2");
configObjectService.update(rname, id, json(config)).getOrThrow();
}
/**
* mock(ConfigurationAdmin.class) requires enough when() clauses to be functional to justify building this as an
* explicit inner class for readability and debugging.
*/
private class MockConfigurationAdmin implements ConfigurationAdmin {
Map<String,Configuration> configurations = new HashMap<String, Configuration>();
@Override
public Configuration createFactoryConfiguration(String factoryPid) throws IOException {
Configuration config = new MockConfiguration();
configurations.put(factoryPid, config);
return config;
}
@Override
public Configuration createFactoryConfiguration(String factoryPid, String location) throws IOException {
Configuration config = new MockConfiguration();
configurations.put(factoryPid, config);
return config;
}
@Override
public Configuration getConfiguration(String pid, String location) throws IOException {
return configurations.containsKey(pid) ? configurations.get(pid) : null;
}
@Override
public Configuration getConfiguration(String pid) throws IOException {
return configurations.containsKey(pid) ? configurations.get(pid) : null;
}
@Override
public Configuration[] listConfigurations(String filter) throws IOException, InvalidSyntaxException {
List<Configuration> configs = new ArrayList<Configuration>();
for (String key : configurations.keySet()) {
if (filter.contains(key)) {
configs.add(configurations.get(key));
}
}
if (!configs.isEmpty()) {
return configs.toArray(new Configuration[configs.size()]);
}
return null;
}
}
@SuppressWarnings("rawtypes")
private class MockConfiguration implements Configuration {
String pid = "pid";
Dictionary dictionary = null;
Boolean deleted = false;
String bundleLocation = "root";
@Override
public String getPid() {
return deleted ? null : pid;
}
@Override
public Dictionary getProperties() {
return deleted ? null : dictionary;
}
@Override
public void update(Dictionary properties) throws IOException {
dictionary = properties;
}
@Override
public void delete() throws IOException {
deleted = true;
}
@Override
public String getFactoryPid() {
return deleted ? null : pid;
}
@Override
public void update() throws IOException {
}
@Override
public void setBundleLocation(String bundleLocation) {
this.bundleLocation = bundleLocation;
}
@Override
public String getBundleLocation() {
return deleted ? null : bundleLocation;
}
}
}