/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.ambari.server.stack;

import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.state.CommandScriptDefinition;
import org.apache.ambari.server.state.ComponentInfo;
import org.apache.ambari.server.state.CredentialStoreInfo;
import org.apache.ambari.server.state.CustomCommandDefinition;
import org.apache.ambari.server.state.PropertyInfo;
import org.apache.ambari.server.state.ServiceInfo;
import org.apache.ambari.server.state.ServiceOsSpecific;
import org.apache.ambari.server.state.ServicePropertyInfo;
import org.apache.ambari.server.state.SingleSignOnInfo;
import org.junit.Test;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;

/**
 * ServiceModule unit tests.
 */
public class ServiceModuleTest {

  @Test
  public void testResolve_Comment() throws Exception {
    String comment = "test comment";

    // comment specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();
    info.setComment(comment);

    ServiceModule service = resolveService(info, parentInfo);
    assertEquals(comment, service.getModuleInfo().getComment());

    // comment specified in parent only
    info.setComment(null);
    parentInfo.setComment(comment);

    service = resolveService(info, parentInfo);
    assertEquals(comment, service.getModuleInfo().getComment());

    // set in both
    info.setComment(comment);
    parentInfo.setComment("other comment");

    service = resolveService(info, parentInfo);
    assertEquals(comment, service.getModuleInfo().getComment());
  }

  @Test
  public void testResolve_DisplayName() throws Exception {
    String displayName = "test_display_name";

    // specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();
    info.setDisplayName(displayName);

    ServiceModule service = resolveService(info, parentInfo);
    assertEquals(displayName, service.getModuleInfo().getDisplayName());

    // specified in parent only
    info.setDisplayName(null);
    parentInfo.setDisplayName(displayName);

    service = resolveService(info, parentInfo);
    assertEquals(displayName, service.getModuleInfo().getDisplayName());

    // specified in both
    info.setDisplayName(displayName);
    parentInfo.setDisplayName("other display name");

    service = resolveService(info, parentInfo);
    assertEquals(displayName, service.getModuleInfo().getDisplayName());
  }

  @Test
  public void testResolve_Version() throws Exception {
    String version = "1.1";

    // specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();
    info.setVersion(version);

    ServiceModule service = resolveService(info, parentInfo);
    assertEquals(version, service.getModuleInfo().getVersion());

    // specified in parent only
    info.setVersion(null);
    parentInfo.setVersion(version);

    service = resolveService(info, parentInfo);
    assertEquals(version, service.getModuleInfo().getVersion());

    // specified in both
    info.setVersion(version);
    parentInfo.setVersion("1.0");

    service = resolveService(info, parentInfo);
    assertEquals(version, service.getModuleInfo().getVersion());
  }

  @Test
  public void testResolve_RequiredServices() throws Exception {
    List<String> requiredServices = new ArrayList<>();
    requiredServices.add("foo");
    requiredServices.add("bar");

    // specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();
    info.setRequiredServices(requiredServices);

    ServiceModule service = resolveService(info, parentInfo);
    assertEquals(requiredServices, service.getModuleInfo().getRequiredServices());

    // specified in parent only
    info.setRequiredServices(null);
    parentInfo.setRequiredServices(requiredServices);

    service = resolveService(info, parentInfo);
    assertEquals(requiredServices, service.getModuleInfo().getRequiredServices());

    // specified in both
    info.setRequiredServices(requiredServices);
    parentInfo.setRequiredServices(Collections.singletonList("other"));

    service = resolveService(info, parentInfo);
    assertEquals(requiredServices, service.getModuleInfo().getRequiredServices());

    // not set in either
    info.setRequiredServices(null);
    parentInfo.setRequiredServices(null);

    service = resolveService(info, parentInfo);
    assertTrue(service.getModuleInfo().getRequiredServices().isEmpty());
  }

  @Test
  public void testResolve_RestartRequiredAfterChange() throws Exception {
    Boolean isRestartRequired = true;

    // specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();
    info.setRestartRequiredAfterChange(isRestartRequired);

    ServiceModule service = resolveService(info, parentInfo);
    assertEquals(isRestartRequired, service.getModuleInfo().isRestartRequiredAfterChange());

    // specified in parent only
    info.setRestartRequiredAfterChange(null);
    parentInfo.setRestartRequiredAfterChange(isRestartRequired);

    service = resolveService(info, parentInfo);
    assertEquals(isRestartRequired, service.getModuleInfo().isRestartRequiredAfterChange());

    // specified in both
    info.setRestartRequiredAfterChange(isRestartRequired);
    parentInfo.setRestartRequiredAfterChange(false);

    service = resolveService(info, parentInfo);
    assertEquals(isRestartRequired, service.getModuleInfo().isRestartRequiredAfterChange());
  }

  @Test
  public void testResolve_MonitoringService() throws Exception {
    Boolean isMonitoringService = true;

    // specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();
    info.setMonitoringService(isMonitoringService);

    ServiceModule service = resolveService(info, parentInfo);
    assertEquals(isMonitoringService, service.getModuleInfo().isMonitoringService());

    // specified in parent only
    info.setMonitoringService(null);
    parentInfo.setMonitoringService(isMonitoringService);

    service = resolveService(info, parentInfo);
    assertEquals(isMonitoringService, service.getModuleInfo().isMonitoringService());

    // specified in both
    info.setMonitoringService(isMonitoringService);
    parentInfo.setMonitoringService(false);

    service = resolveService(info, parentInfo);
    assertEquals(isMonitoringService, service.getModuleInfo().isMonitoringService());
  }

  @Test
  public void testResolve_OsSpecifics() throws Exception {
    Map<String, ServiceOsSpecific> osSpecifics = new HashMap<>();
    osSpecifics.put("foo", new ServiceOsSpecific());

    // specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();
    info.setOsSpecifics(osSpecifics);

    ServiceModule service = resolveService(info, parentInfo);
    assertEquals(osSpecifics, service.getModuleInfo().getOsSpecifics());

    // specified in parent only
    info.setOsSpecifics(null);
    parentInfo.setOsSpecifics(osSpecifics);

    service = resolveService(info, parentInfo);
    assertEquals(osSpecifics, service.getModuleInfo().getOsSpecifics());

    // specified in both
    Map<String, ServiceOsSpecific> osSpecifics2 = new HashMap<>();
    osSpecifics.put("bar", new ServiceOsSpecific());

    info.setOsSpecifics(osSpecifics);
    parentInfo.setOsSpecifics(osSpecifics2);

    service = resolveService(info, parentInfo);
    assertEquals(osSpecifics, service.getModuleInfo().getOsSpecifics());
  }

  @Test
  public void testResolve_CommandScript() throws Exception {
    CommandScriptDefinition commandScript = new CommandScriptDefinition();

    // specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();
    info.setCommandScript(commandScript);

    ServiceModule service = resolveService(info, parentInfo);
    assertEquals(commandScript, service.getModuleInfo().getCommandScript());

    // specified in parent only
    info.setCommandScript(null);
    parentInfo.setCommandScript(commandScript);

    service = resolveService(info, parentInfo);
    assertEquals(commandScript, service.getModuleInfo().getCommandScript());

    // specified in both
    CommandScriptDefinition commandScript2 = new CommandScriptDefinition();

    info.setCommandScript(commandScript);
    parentInfo.setCommandScript(commandScript2);

    service = resolveService(info, parentInfo);
    assertEquals(commandScript, service.getModuleInfo().getCommandScript());
  }

  @Test
  public void testResolve_ServicePackageFolder() throws Exception {
    String servicePackageFolder = "packageDir";

    // specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();

    ServiceModule child = createServiceModule(info);
    ServiceModule parent = createServiceModule(parentInfo);

    // set in the module constructor from a value obtained from service directory
    assertEquals("packageDir", child.getModuleInfo().getServicePackageFolder());
    parent.getModuleInfo().setServicePackageFolder(null);

    resolveService(child, parent);
    assertEquals(servicePackageFolder, child.getModuleInfo().getServicePackageFolder());

    // specified in parent only
    child = createServiceModule(info);
    parent = createServiceModule(parentInfo);
    parent.getModuleInfo().setServicePackageFolder(servicePackageFolder);
    child.getModuleInfo().setServicePackageFolder(null);

    resolveService(child, parent);
    assertEquals(servicePackageFolder, child.getModuleInfo().getServicePackageFolder());

    // specified in both
    child = createServiceModule(info);
    parent = createServiceModule(parentInfo);
    parent.getModuleInfo().setServicePackageFolder("someOtherDir");
    child.getModuleInfo().setServicePackageFolder(servicePackageFolder);

    resolveService(child, parent);
    assertEquals(servicePackageFolder, child.getModuleInfo().getServicePackageFolder());
  }

  @Test
  public void testResolve_MetricsFile() throws Exception {
    File metricsFile = new File("testMetricsFile");

    // specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();

    ServiceModule child = createServiceModule(info);
    ServiceModule parent = createServiceModule(parentInfo);

    // set in the module constructor from a value obtained from service directory which is mocked
    assertEquals(metricsFile, child.getModuleInfo().getMetricsFile());
    parent.getModuleInfo().setMetricsFile(null);

    resolveService(child, parent);
    assertEquals(metricsFile, child.getModuleInfo().getMetricsFile());

    // specified in parent only
    child = createServiceModule(info);
    parent = createServiceModule(parentInfo);
    parent.getModuleInfo().setMetricsFile(metricsFile);
    child.getModuleInfo().setMetricsFile(null);

    resolveService(child, parent);
    assertEquals(metricsFile, child.getModuleInfo().getMetricsFile());

    // specified in both
    child = createServiceModule(info);
    parent = createServiceModule(parentInfo);
    parent.getModuleInfo().setMetricsFile(new File("someOtherDir"));
    child.getModuleInfo().setMetricsFile(metricsFile);

    resolveService(child, parent);
    assertEquals(metricsFile, child.getModuleInfo().getMetricsFile());
  }

  @Test
  public void testResolve_AlertsFile() throws Exception {
    File alertsFile = new File("testAlertsFile");

    // specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();

    ServiceModule child = createServiceModule(info);
    ServiceModule parent = createServiceModule(parentInfo);

    // set in the module constructor from a value obtained from service directory which is mocked
    assertEquals(alertsFile, child.getModuleInfo().getAlertsFile());
    parent.getModuleInfo().setAlertsFile(null);

    resolveService(child, parent);
    assertEquals(alertsFile, child.getModuleInfo().getAlertsFile());

    // specified in parent only
    child = createServiceModule(info);
    parent = createServiceModule(parentInfo);
    parent.getModuleInfo().setAlertsFile(alertsFile);
    child.getModuleInfo().setAlertsFile(null);

    resolveService(child, parent);
    assertEquals(alertsFile, child.getModuleInfo().getAlertsFile());

    // specified in both
    child = createServiceModule(info);
    parent = createServiceModule(parentInfo);
    parent.getModuleInfo().setAlertsFile(new File("someOtherDir"));
    child.getModuleInfo().setAlertsFile(alertsFile);

    resolveService(child, parent);
    assertEquals(alertsFile, child.getModuleInfo().getAlertsFile());
  }

  @Test
  public void testResolve_KerberosDescriptorFile() throws Exception {
    File kerberosDescriptorFile = new File("testKerberosDescriptorFile");

    // specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();

    ServiceModule child = createServiceModule(info);
    ServiceModule parent = createServiceModule(parentInfo);

    // set in the module constructor from a value obtained from service directory which is mocked
    assertEquals(kerberosDescriptorFile, child.getModuleInfo().getKerberosDescriptorFile());
    parent.getModuleInfo().setKerberosDescriptorFile(null);

    resolveService(child, parent);
    assertEquals(kerberosDescriptorFile, child.getModuleInfo().getKerberosDescriptorFile());

    // specified in parent only
    child = createServiceModule(info);
    parent = createServiceModule(parentInfo);
    parent.getModuleInfo().setKerberosDescriptorFile(kerberosDescriptorFile);
    child.getModuleInfo().setKerberosDescriptorFile(null);

    resolveService(child, parent);
    assertEquals(kerberosDescriptorFile, child.getModuleInfo().getKerberosDescriptorFile());

    // specified in both
    child = createServiceModule(info);
    parent = createServiceModule(parentInfo);
    parent.getModuleInfo().setKerberosDescriptorFile(new File("someOtherDir"));
    child.getModuleInfo().setKerberosDescriptorFile(kerberosDescriptorFile);

    resolveService(child, parent);
    assertEquals(kerberosDescriptorFile, child.getModuleInfo().getKerberosDescriptorFile());
  }

  @Test
  public void testResolveServiceAdvisor() throws Exception {
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();
    ServiceModule child = createServiceModule(info);
    ServiceModule parent = createServiceModule(parentInfo);

    // Parent is NULL, Child is NULL => Child defaults to PYTHON
    parent.getModuleInfo().setServiceAdvisorType(null);
    child.getModuleInfo().setServiceAdvisorType(null);
    resolveService(child, parent);
    assertEquals(ServiceInfo.ServiceAdvisorType.PYTHON, child.getModuleInfo().getServiceAdvisorType());

    // Parent is NULL, Child is JAVA => Child is JAVA
    child.getModuleInfo().setServiceAdvisorType(ServiceInfo.ServiceAdvisorType.JAVA);
    resolveService(child, parent);
    assertEquals(ServiceInfo.ServiceAdvisorType.JAVA, child.getModuleInfo().getServiceAdvisorType());

    // Parent is JAVA, Child is NULL => Child inherits JAVA
    parent.getModuleInfo().setServiceAdvisorType(ServiceInfo.ServiceAdvisorType.JAVA);
    child.getModuleInfo().setServiceAdvisorType(null);
    resolveService(child, parent);
    assertEquals(ServiceInfo.ServiceAdvisorType.JAVA, child.getModuleInfo().getServiceAdvisorType());

    // Parent is JAVA, Child is PYTHON => Child overrides and keeps PYTHON
    parent.getModuleInfo().setServiceAdvisorType(null);
    child.getModuleInfo().setServiceAdvisorType(ServiceInfo.ServiceAdvisorType.PYTHON);
    resolveService(child, parent);
    assertEquals(ServiceInfo.ServiceAdvisorType.PYTHON, child.getModuleInfo().getServiceAdvisorType());
  }

  @Test
  public void testResolve_UpgradeCheckDirectory() throws Exception {
    File checks = new File("checks");

    // check directory specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();
    ServiceModule child = createServiceModule(info);
    ServiceModule parent = createServiceModule(parentInfo);
    child.getModuleInfo().setChecksFolder(checks);
    resolveService(child, parent);
    assertEquals(checks.getPath(), child.getModuleInfo().getChecksFolder().getPath());

    // check directory specified in parent only
    child = createServiceModule(info);
    parent = createServiceModule(parentInfo);
    parent.getModuleInfo().setChecksFolder(checks);
    resolveService(child, parent);
    assertEquals(checks.getPath(), child.getModuleInfo().getChecksFolder().getPath());

    // check directory set in both
    info.setChecksFolder(checks);
    child = createServiceModule(info);
    child.getModuleInfo().setChecksFolder(checks);
    parent = createServiceModule(parentInfo);
    parent.getModuleInfo().setChecksFolder(new File("other"));
    resolveService(child, parent);
    assertEquals(checks.getPath(), child.getModuleInfo().getChecksFolder().getPath());
  }

  @Test
  public void testResolve_ServerActionDirectory() throws Exception {
    File serverActions = new File("server_actions");

    // check directory specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();
    ServiceModule child = createServiceModule(info);
    ServiceModule parent = createServiceModule(parentInfo);
    child.getModuleInfo().setServerActionsFolder(serverActions);
    resolveService(child, parent);
    assertEquals(serverActions.getPath(), child.getModuleInfo().getServerActionsFolder().getPath());

    // check directory specified in parent only
    child = createServiceModule(info);
    parent = createServiceModule(parentInfo);
    parent.getModuleInfo().setServerActionsFolder(serverActions);
    resolveService(child, parent);
    assertEquals(serverActions.getPath(), child.getModuleInfo().getServerActionsFolder().getPath());

    // check directory set in both
    info.setServerActionsFolder(serverActions);
    child = createServiceModule(info);
    child.getModuleInfo().setServerActionsFolder(serverActions);
    parent = createServiceModule(parentInfo);
    parent.getModuleInfo().setServerActionsFolder(new File("other"));
    resolveService(child, parent);
    assertEquals(serverActions.getPath(), child.getModuleInfo().getServerActionsFolder().getPath());
  }

  @Test
  public void testResolve_CustomCommands() throws Exception {
    List<CustomCommandDefinition> customCommands = new ArrayList<>();
    CustomCommandDefinition cmd1 = new CustomCommandDefinition();
    setPrivateField(cmd1, "name", "cmd1");
    setPrivateField(cmd1, "background", false);
    CustomCommandDefinition cmd2 = new CustomCommandDefinition();
    setPrivateField(cmd2, "name", "cmd2");
    customCommands.add(cmd1);
    customCommands.add(cmd2);

    // specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();
    info.setCustomCommands(customCommands);

    ServiceModule service = resolveService(info, parentInfo);
    assertEquals(customCommands, service.getModuleInfo().getCustomCommands());

    // specified in parent only
    info.setCustomCommands(null);
    parentInfo.setCustomCommands(customCommands);

    service = resolveService(info, parentInfo);
    assertEquals(customCommands, service.getModuleInfo().getCustomCommands());

    // specified in both
    List<CustomCommandDefinition> parentCustomCommands = new ArrayList<>();
    CustomCommandDefinition cmd3 = new CustomCommandDefinition();
    setPrivateField(cmd3, "name", "cmd1");
    setPrivateField(cmd3, "background", true);
    CustomCommandDefinition cmd4 = new CustomCommandDefinition();
    setPrivateField(cmd4, "name", "cmd4");
    parentCustomCommands.add(cmd3);
    parentCustomCommands.add(cmd4);

    info.setCustomCommands(customCommands);
    parentInfo.setCustomCommands(parentCustomCommands);

    service = resolveService(info, parentInfo);
    Collection<CustomCommandDefinition> mergedCommands =  service.getModuleInfo().getCustomCommands();
    assertEquals(3, mergedCommands.size());
    assertTrue(mergedCommands.contains(cmd2));
    assertTrue(mergedCommands.contains(cmd3));
    assertTrue(mergedCommands.contains(cmd4));

    // not set in either
    info.setCustomCommands(null);
    parentInfo.setCustomCommands(null);

    service = resolveService(info, parentInfo);
    assertTrue(service.getModuleInfo().getCustomCommands().isEmpty());
  }

  @Test
  public void testResolve_ConfigDependencies() throws Exception {
    List<String> configDependencies = new ArrayList<>();
    configDependencies.add("foo");
    configDependencies.add("bar");

    // specified in child only
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();
    info.setConfigDependencies(configDependencies);

    ServiceModule service = resolveService(info, parentInfo);
    assertEquals(configDependencies, service.getModuleInfo().getConfigDependencies());

    // specified in parent only
    info.setConfigDependencies(null);
    parentInfo.setConfigDependencies(configDependencies);

    service = resolveService(info, parentInfo);
    assertEquals(configDependencies, service.getModuleInfo().getConfigDependencies());

    // specified in both
    List<String> parentCustomCommands = new ArrayList<>();
    parentCustomCommands.add("bar");
    parentCustomCommands.add("other");

    info.setConfigDependencies(configDependencies);
    parentInfo.setConfigDependencies(parentCustomCommands);

    service = resolveService(info, parentInfo);
    Collection<String> mergedConfigDependencies =  service.getModuleInfo().getConfigDependencies();
    assertEquals(3, mergedConfigDependencies.size());
    assertTrue(mergedConfigDependencies.contains("foo"));
    assertTrue(mergedConfigDependencies.contains("bar"));
    assertTrue(mergedConfigDependencies.contains("other"));

    // not set in either
    info.setConfigDependencies(null);
    parentInfo.setConfigDependencies(null);

    service = resolveService(info, parentInfo);
    assertTrue(service.getModuleInfo().getConfigDependencies().isEmpty());
  }

  @Test
  public void testResolve_Components() throws Exception {
    // resolve should merge the child component collections
    // components 1, 2 and XX are set on the parent
    // components 1, 4 and XX are set on the child
    // component XX is marked for delete on the child and shouldn't be included
    // component 1 should be merged
    // both non-intersecting components 2 and 4 should be included
    ComponentInfo info1 = new ComponentInfo();
    info1.setName("1");
    ComponentInfo info2 = new ComponentInfo();
    info2.setName("2");
    ComponentInfo XX = new ComponentInfo();
    XX.setName("XX");

    ComponentInfo info3 = new ComponentInfo();
    // overlaps with info1
    info3.setName("1");
    info3.setCardinality("ALL");
    info3.setCategory("category");
    ComponentInfo info4 = new ComponentInfo();
    info4.setName("4");
    ComponentInfo info5 = new ComponentInfo();
    // overlaps with componentToBeDeleted
    info5.setName("XX");
    info5.setDeleted(true);

    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();

    //todo: getComponents() should return a protective copy, but for now there is no set/add method
    List<ComponentInfo> childComponents = info.getComponents();
    childComponents.add(info3);
    childComponents.add(info4);
    childComponents.add(info5);

    List<ComponentInfo> parentComponents = parentInfo.getComponents();
    parentComponents.add(info1);
    parentComponents.add(info2);

    ServiceModule child = createServiceModule(info);
    ServiceModule parent = createServiceModule(parentInfo);

    resolveService(child, parent);

    List<ComponentInfo> components = child.getModuleInfo().getComponents();
    assertEquals(3, components.size());

    Map<String, ComponentInfo> mergedComponents = new HashMap<>();
    for (ComponentInfo component : components) {
      mergedComponents.put(component.getName(), component);
    }
    assertTrue(mergedComponents.containsKey("1"));
    assertTrue(mergedComponents.containsKey("2"));
    assertTrue(mergedComponents.containsKey("4"));

    // ensure that overlapping components were merged.
    //don't test all properties, this is done in ComponentModuleTest
    assertEquals("ALL", mergedComponents.get("1").getCardinality());
    assertEquals("category", mergedComponents.get("1").getCategory());
  }

  @Test
  public void testResolve_Configuration__properties() throws Exception {
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();

    // child configurations
    //FOO
    Collection<PropertyInfo> childFooProperties = new ArrayList<>();
    PropertyInfo childProp1 = new PropertyInfo();
    childProp1.setName("childName1");
    childProp1.setValue("childVal1");
    childFooProperties.add(childProp1);

    //BAR : Doesn't inherit parents BAR due to attribute Supports.DO_NOT_EXTEND
    Collection<PropertyInfo> childBarProperties = new ArrayList<>();
    PropertyInfo childProp2 = new PropertyInfo();
    childProp2.setName("childName2");
    childProp2.setValue("childVal2");
    childBarProperties.add(childProp2);

    // add attributes for BAR
    Map<String, String> attributes = new HashMap<>();
    attributes.put(ConfigurationInfo.Supports.DO_NOT_EXTEND.getXmlAttributeName(), "true");

    // create child config modules
    ConfigurationModule childConfigModule1 = createConfigurationModule("FOO", childFooProperties);
    ConfigurationModule childConfigModule2 = createConfigurationModule("BAR", childBarProperties, attributes);
    Collection<ConfigurationModule> childModules = new ArrayList<>();
    childModules.add(childConfigModule1);
    childModules.add(childConfigModule2);

    // parent configurations
    //FOO
    Collection<PropertyInfo> parentFooProperties = new ArrayList<>();
    PropertyInfo parentProp1 = new PropertyInfo();
    parentProp1.setName("parentName1");
    parentProp1.setValue("parentVal1");
    parentFooProperties.add(parentProp1);
    PropertyInfo parentProp12 = new PropertyInfo();
    // overwritten by child
    parentProp12.setName("childName1");
    parentProp12.setValue("parentVal1");
    parentFooProperties.add(parentProp12);

    //BAR
    Collection<PropertyInfo> parentBarProperties = new ArrayList<>();
    PropertyInfo parentProp2 = new PropertyInfo();
    parentProp2.setName("parentName2");
    parentProp2.setValue("parentVal2");
    parentBarProperties.add(parentProp2);

    //OTHER
    Collection<PropertyInfo> parentOtherProperties = new ArrayList<>();
    PropertyInfo parentProp3 = new PropertyInfo();
    parentProp3.setName("parentName3");
    parentProp3.setValue("parentVal3");
    parentOtherProperties.add(parentProp3);

    // create parent config modules
    ConfigurationModule parentConfigModule1 = createConfigurationModule("FOO", parentFooProperties);
    ConfigurationModule parentConfigModule2 = createConfigurationModule("BAR", parentBarProperties);
    ConfigurationModule parentConfigModule3 = createConfigurationModule("OTHER", parentOtherProperties);
    Collection<ConfigurationModule> parentModules = new ArrayList<>();
    parentModules.add(parentConfigModule1);
    parentModules.add(parentConfigModule2);
    parentModules.add(parentConfigModule3);

    // create service modules
    ServiceModule child = createServiceModule(info, childModules);
    ServiceModule parent = createServiceModule(parentInfo, parentModules);

    // resolve child with parent
    resolveService(child, parent);

    // assertions
    List<PropertyInfo> mergedProperties = child.getModuleInfo().getProperties();
    assertEquals(4, mergedProperties.size());

    Map<String, PropertyInfo> mergedPropertyMap = new HashMap<>();
    for (PropertyInfo prop : mergedProperties) {
      mergedPropertyMap.put(prop.getName(), prop);
    }

    // filename is null for all props because that is set in ConfigurationDirectory which is mocked
    assertEquals("childVal1", mergedPropertyMap.get("childName1").getValue());
    assertEquals("childVal2", mergedPropertyMap.get("childName2").getValue());
    assertEquals("parentVal1", mergedPropertyMap.get("parentName1").getValue());
    assertEquals("parentVal3", mergedPropertyMap.get("parentName3").getValue());

    Map<String, Map<String, Map<String, String>>> childAttributes = child.getModuleInfo().getConfigTypeAttributes();
    Map<String, Map<String, Map<String, String>>> parentAttributes = parent.getModuleInfo().getConfigTypeAttributes();

    assertEquals(3, childAttributes.size());
    assertAttributes(childAttributes.get("FOO"), Collections.emptyMap());
    assertAttributes(childAttributes.get("BAR"), attributes);
    assertAttributes(childAttributes.get("OTHER"), Collections.emptyMap());

    assertEquals(3, parentAttributes.size());
    assertAttributes(parentAttributes.get("FOO"), Collections.emptyMap());
    assertAttributes(parentAttributes.get("BAR"), Collections.emptyMap());
    assertAttributes(parentAttributes.get("OTHER"), Collections.emptyMap());
  }

  @Test
  public void testResolve_Service__selection() throws Exception {
    ServiceInfo firstInfo = new ServiceInfo();
    ServiceInfo secondInfo = new ServiceInfo();
    ServiceInfo thirdInfo = new ServiceInfo();

    firstInfo.setSelection(ServiceInfo.Selection.MANDATORY);

    resolveService(secondInfo, firstInfo);

    assertEquals(secondInfo.getSelection(), ServiceInfo.Selection.MANDATORY);

    thirdInfo.setSelection(ServiceInfo.Selection.TECH_PREVIEW);

    resolveService(thirdInfo, secondInfo);

    assertEquals(thirdInfo.getSelection(), ServiceInfo.Selection.TECH_PREVIEW);
  }

  @Test
  public void testResolve_Configuration__attributes() throws Exception {
    ServiceInfo info = new ServiceInfo();
    ServiceInfo parentInfo = new ServiceInfo();

    // child configurations
    //FOO
    Collection<PropertyInfo> childFooProperties = new ArrayList<>();
    PropertyInfo childProp1 = new PropertyInfo();
    childProp1.setName("childName1");
    childProp1.setValue("childVal1");
    childFooProperties.add(childProp1);

    // add attributes for parent FOO
    Map<String, String> childFooAttributes = new HashMap<>();
    // override parents value
    childFooAttributes.put(ConfigurationInfo.Supports.ADDING_FORBIDDEN.getXmlAttributeName(), "false");

    // create child config modules
    ConfigurationModule childConfigModule1 = createConfigurationModule("FOO", childFooProperties, childFooAttributes);
    Collection<ConfigurationModule> childModules = new ArrayList<>();
    childModules.add(childConfigModule1);

    // parent configurations
    //FOO
    Collection<PropertyInfo> parentFooProperties = new ArrayList<>();
    PropertyInfo parentProp1 = new PropertyInfo();
    parentProp1.setName("parentName1");
    parentProp1.setValue("parentVal1");
    parentFooProperties.add(parentProp1);

    // add attributes for parent FOO
    Map<String, String> parentFooAttributes = new HashMap<>();
    // child will inherit
