/**
 * 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.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.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.CustomCommandDefinition;
import org.apache.ambari.server.state.PropertyInfo;
import org.apache.ambari.server.state.ServiceInfo;
import org.apache.ambari.server.state.ServiceOsSpecific;
import org.junit.Test;

/**
 * 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<String>();
    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<String, ServiceOsSpecific>();
    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<String, ServiceOsSpecific>();
    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 testResolve_CustomCommands() throws Exception {
    List<CustomCommandDefinition> customCommands = new ArrayList<CustomCommandDefinition>();
    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>();
    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<String>();
    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<String>();
    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");
    info1.setCardinality("ALL");
    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.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<String, ComponentInfo>();
    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>();
    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>();
    PropertyInfo childProp2 = new PropertyInfo();
    childProp2.setName("childName2");
    childProp2.setValue("childVal2");
    childBarProperties.add(childProp2);

    // add attributes for BAR
    Map<String, String> attributes = new HashMap<String, String>();
    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<ConfigurationModule>();
    childModules.add(childConfigModule1);
    childModules.add(childConfigModule2);

    // parent configurations
    //FOO
    Collection<PropertyInfo> parentFooProperties = new ArrayList<PropertyInfo>();
    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>();
    PropertyInfo parentProp2 = new PropertyInfo();
    parentProp2.setName("parentName2");
    parentProp2.setValue("parentVal2");
    parentBarProperties.add(parentProp2);

    //OTHER
    Collection<PropertyInfo> parentOtherProperties = new ArrayList<PropertyInfo>();
    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<ConfigurationModule>();
    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<String, PropertyInfo>();
    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.<String, String>emptyMap());
    assertAttributes(childAttributes.get("BAR"), attributes);
    assertAttributes(childAttributes.get("OTHER"), Collections.<String, String>emptyMap());

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

  @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>();
    PropertyInfo childProp1 = new PropertyInfo();
    childProp1.setName("childName1");
    childProp1.setValue("childVal1");
    childFooProperties.add(childProp1);

    // add attributes for parent FOO
    Map<String, String> childFooAttributes = new HashMap<String, String>();
    // 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<ConfigurationModule>();
    childModules.add(childConfigModule1);

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

    // add attributes for parent FOO
    Map<String, String> parentFooAttributes = new HashMap<String, String>();
    // child will inherit
    parentFooAttributes.put(ConfigurationInfo.Supports.FINAL.getXmlAttributeName(), "true");
    // child will override
    parentFooAttributes.put(ConfigurationInfo.Supports.ADDING_FORBIDDEN.getXmlAttributeName(), "true");

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


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

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

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

    // assertions
    Map<String, Map<String, Map<String, String>>> childTypeAttributes = child.getModuleInfo().getConfigTypeAttributes();
    Map<String, Map<String, Map<String, String>>> parentTypeAttributes = parent.getModuleInfo().getConfigTypeAttributes();
    assertTrue(childTypeAttributes.containsKey("FOO"));
    Map<String, Map<String, String>> mergedChildFooAttributes = childTypeAttributes.get("FOO");
    assertTrue(mergedChildFooAttributes.containsKey(ConfigurationInfo.Supports.KEYWORD));
    // inherited value
    assertEquals("true", mergedChildFooAttributes.get(ConfigurationInfo.Supports.KEYWORD).
        get(ConfigurationInfo.Supports.valueOf("FINAL").getPropertyName()));
    // overridden value
    assertEquals("false", mergedChildFooAttributes.get(ConfigurationInfo.Supports.KEYWORD).
        get(ConfigurationInfo.Supports.valueOf("ADDING_FORBIDDEN").getPropertyName()));

    assertEquals(2, childTypeAttributes.size());

    assertEquals(2, parentTypeAttributes.size());
    assertAttributes(parentTypeAttributes.get("FOO"), parentFooAttributes);
    assertAttributes(parentTypeAttributes.get("BAR"), Collections.<String, String>emptyMap());
  }

  @Test
  public void testResolve_Configuration__ExcludedTypes() throws Exception {
    ServiceInfo info = new ServiceInfo();
    info.setExcludedConfigTypes(Collections.singleton("BAR"));

    //FOO
    Collection<PropertyInfo> fooProperties = new ArrayList<PropertyInfo>();
    PropertyInfo prop1 = new PropertyInfo();
    prop1.setName("name1");
    prop1.setValue("val1");
    fooProperties.add(prop1);
    PropertyInfo prop2 = new PropertyInfo();
    prop2.setName("name2");
    prop2.setValue("val2");
    fooProperties.add(prop2);

    //BAR
    Collection<PropertyInfo> barProperties = new ArrayList<PropertyInfo>();
    PropertyInfo prop3 = new PropertyInfo();
    prop3.setName("name1");
    prop3.setValue("val3");
    barProperties.add(prop3);

    //OTHER
    Collection<PropertyInfo> otherProperties = new ArrayList<PropertyInfo>();
    PropertyInfo prop4 = new PropertyInfo();
    prop4.setName("name1");
    prop4.setValue("val4");
    otherProperties.add(prop4);

    ConfigurationModule configModule1 = createConfigurationModule("FOO", fooProperties);
    ConfigurationModule configModule2 = createConfigurationModule("BAR", barProperties);
    ConfigurationModule configModule3 = createConfigurationModule("OTHER", otherProperties);
    Collection<ConfigurationModule> configModules = new ArrayList<ConfigurationModule>();
    configModules.add(configModule1);
    configModules.add(configModule2);
    configModules.add(configModule3);

    ServiceModule service = createServiceModule(info, configModules);

    List<PropertyInfo> properties = service.getModuleInfo().getProperties();
    assertEquals(4, properties.size());

    Map<String, Map<String, Map<String, String>>> attributes = service.getModuleInfo().getConfigTypeAttributes();
    assertEquals(2, attributes.size());
    assertTrue(attributes.containsKey("FOO"));
    assertTrue(attributes.containsKey("OTHER"));
  }

  @Test
  public void testResolve_Configuration__ExcludedTypes__ParentType() throws Exception {
    // child
    ServiceInfo info = new ServiceInfo();
    info.setExcludedConfigTypes(Collections.singleton("BAR"));

    //FOO
    Collection<PropertyInfo> fooProperties = new ArrayList<PropertyInfo>();
    PropertyInfo prop1 = new PropertyInfo();
    prop1.setName("name1");
    prop1.setValue("val1");
    fooProperties.add(prop1);
    PropertyInfo prop2 = new PropertyInfo();
    prop2.setName("name2");
    prop2.setValue("val2");
    fooProperties.add(prop2);

    ConfigurationModule childConfigModule = createConfigurationModule("FOO", fooProperties);
    Collection<ConfigurationModule> childConfigModules = new ArrayList<ConfigurationModule>();
    childConfigModules.add(childConfigModule);

    // parent
    ServiceInfo parentInfo = new ServiceInfo();

    //BAR
    Collection<PropertyInfo> barProperties = new ArrayList<PropertyInfo>();
    PropertyInfo prop3 = new PropertyInfo();
    prop3.setName("name1");
    prop3.setValue("val3");
    barProperties.add(prop3);

    ConfigurationModule parentConfigModule = createConfigurationModule("BAR", barProperties);
    Collection<ConfigurationModule> parentConfigModules = new ArrayList<ConfigurationModule>();
    parentConfigModules.add(parentConfigModule);

    // create service modules
    ServiceModule service = createServiceModule(info, childConfigModules);
    ServiceModule parentService = createServiceModule(parentInfo, parentConfigModules);
    // resolve child with parent
    resolveService(service, parentService);
    // assertions
    List<PropertyInfo> properties = service.getModuleInfo().getProperties();
    assertEquals(2, properties.size());

    Map<String, Map<String, Map<String, String>>> attributes = service.getModuleInfo().getConfigTypeAttributes();
    assertEquals(1, attributes.size());
    assertTrue(attributes.containsKey("FOO"));

    Map<String, Map<String, Map<String, String>>> parentAttributes = parentService.getModuleInfo().getConfigTypeAttributes();
    assertEquals(1, parentAttributes.size());
    assertTrue(parentAttributes.containsKey("BAR"));
  }

  @Test
  public void testMerge_Configuration__ExcludedTypes() throws Exception {
    // child
    ServiceInfo info = new ServiceInfo();
    Set<String> childExcludedConfigTypes = new HashSet<String>();
    childExcludedConfigTypes.add("FOO");
    info.setExcludedConfigTypes(childExcludedConfigTypes);

    //FOO
    Collection<PropertyInfo> fooProperties = new ArrayList<PropertyInfo>();

    ConfigurationModule childConfigModule = createConfigurationModule("FOO", fooProperties);
    Collection<ConfigurationModule> childConfigModules = new ArrayList<ConfigurationModule>();
    childConfigModules.add(childConfigModule);

    // parent
    ServiceInfo parentInfo = new ServiceInfo();
    Set<String> parentExcludedConfigTypes = new HashSet<String>();
    childExcludedConfigTypes.add("BAR");
    info.setExcludedConfigTypes(childExcludedConfigTypes);
    parentInfo.setExcludedConfigTypes(parentExcludedConfigTypes);
    //BAR
    Collection<PropertyInfo> barProperties = new ArrayList<PropertyInfo>();


    ConfigurationModule parentConfigModule = createConfigurationModule("BAR", barProperties);
    Collection<ConfigurationModule> parentConfigModules = new ArrayList<ConfigurationModule>();
    parentConfigModules.add(parentConfigModule);

    // create service modules
    ServiceModule service = createServiceModule(info, childConfigModules);
    ServiceModule parentService = createServiceModule(parentInfo, parentConfigModules);
    // resolve child with parent

    resolveService(service, parentService);

    //resolveService(service, parentService);
    assertEquals(2, service.getModuleInfo().getExcludedConfigTypes().size());
  }

  @Test
  public void testServiceCheckRegistered() throws Exception {
    ServiceInfo info = new ServiceInfo();
    info.setName("service1");
    info.setCommandScript(createNiceMock(CommandScriptDefinition.class));

    StackContext context = createStackContext(info.getName(), true);
    ServiceModule service = createServiceModule(info, Collections.<ConfigurationModule>emptySet(), context);
    service.finalizeModule();

    verify(context);
  }

  @Test
  public void testServiceCheckNotRegisteredForDeletedService() throws Exception {
    ServiceInfo info = new ServiceInfo();
    info.setName("service1");
    info.setCommandScript(createNiceMock(CommandScriptDefinition.class));
    info.setDeleted(true);

    StackContext context = createStackContext(info.getName(), false);
    ServiceModule service = createServiceModule(info, Collections.<ConfigurationModule>emptySet(), context);
    service.finalizeModule();

    verify(context);
  }

  private ServiceModule createServiceModule(ServiceInfo serviceInfo) {
    String configType = "type1";

    if (serviceInfo.getName() == null) {
      serviceInfo.setName("service1");
    }

    StackContext context = createStackContext(serviceInfo.getName(), true);
    // no config props
    ConfigurationInfo configInfo = createConfigurationInfo(Collections.<PropertyInfo>emptyList(),
        Collections.<String, String>emptyMap());

    ConfigurationModule module = createConfigurationModule(configType, configInfo);
    ConfigurationDirectory configDirectory = createConfigurationDirectory(Collections.singletonList(module));
    ServiceDirectory serviceDirectory = createServiceDirectory(serviceInfo.getConfigDir(), configDirectory);

    return createServiceModule(context, serviceInfo, serviceDirectory);
  }

  private ServiceModule createServiceModule(ServiceInfo serviceInfo,
                                            Collection<ConfigurationModule> configurations,
                                            StackContext context) {

    if (serviceInfo.getName() == null) {
      serviceInfo.setName("service1");
    }

    ConfigurationDirectory configDirectory = createConfigurationDirectory(configurations);
    ServiceDirectory serviceDirectory = createServiceDirectory(serviceInfo.getConfigDir(), configDirectory);

    return createServiceModule(context, serviceInfo, serviceDirectory);
  }

  private ServiceModule createServiceModule(ServiceInfo serviceInfo, Collection<ConfigurationModule> configurations) {
    String serviceName = serviceInfo.getName();

    if (serviceInfo.getName() == null) {
      serviceInfo.setName("service1");
    }

    return createServiceModule(serviceInfo, configurations, createStackContext(serviceName, true));
  }

  private ServiceModule createServiceModule(StackContext context, ServiceInfo serviceInfo,
                                            ServiceDirectory serviceDirectory) {

    return new ServiceModule(context, serviceInfo, serviceDirectory);
  }

  private ServiceDirectory createServiceDirectory(String dir, ConfigurationDirectory configDir) {

    ServiceDirectory serviceDirectory = createNiceMock(ServiceDirectory.class);

    expect(serviceDirectory.getConfigurationDirectory(dir)).andReturn(configDir).anyTimes();
    expect(serviceDirectory.getMetricsFile()).andReturn(new File("testMetricsFile")).anyTimes();
    expect(serviceDirectory.getAlertsFile()).andReturn(new File("testAlertsFile")).anyTimes();
    expect(serviceDirectory.getKerberosDescriptorFile()).andReturn(new File("testKerberosDescriptorFile")).anyTimes();
    expect(serviceDirectory.getPackageDir()).andReturn("packageDir").anyTimes();
    replay(serviceDirectory);

    return serviceDirectory;
  }

  private ConfigurationDirectory createConfigurationDirectory(Collection<ConfigurationModule> modules) {
    ConfigurationDirectory configDir = createNiceMock(ConfigurationDirectory.class);

    expect(configDir.getConfigurationModules()).andReturn(modules).anyTimes();
    replay(configDir);

    return configDir;
  }

  private ConfigurationModule createConfigurationModule(String configType, ConfigurationInfo info) {
    return new ConfigurationModule(configType, info);
  }

  private ConfigurationModule createConfigurationModule(String configType, Collection<PropertyInfo> properties) {
    ConfigurationInfo info = new ConfigurationInfo(properties, Collections.<String, String>emptyMap());
    return new ConfigurationModule(configType, info);
  }

  private ConfigurationModule createConfigurationModule(String configType,
                                                        Collection<PropertyInfo> properties,
                                                        Map<String, String> attributes) {

    ConfigurationInfo info = new ConfigurationInfo(properties, attributes);
    return new ConfigurationModule(configType, info);
  }

  private ConfigurationInfo createConfigurationInfo(Collection<PropertyInfo> properties,
                                                    Map<String, String> attributes) {

    return new ConfigurationInfo(properties, attributes);
  }

  private StackContext createStackContext(String serviceName, boolean expectServiceRegistration) {
    StackContext context = createStrictMock(StackContext.class);

    if (expectServiceRegistration) {
      context.registerServiceCheck(serviceName);
    }
    replay(context);

    return context;
  }

  private ServiceModule resolveService(ServiceInfo info, ServiceInfo parentInfo) throws AmbariException {
    ServiceModule service = createServiceModule(info);
    ServiceModule parentService = createServiceModule(parentInfo);

    resolveService(service, parentService);
    return service;
  }

  private void resolveService(ServiceModule service, ServiceModule parent) throws AmbariException {
    service.resolve(parent, Collections.<String, StackModule>emptyMap(), Collections.<String, ServiceModule>emptyMap());
    // during runtime this would be called by the Stack module when it's resolve completed
    service.finalizeModule();
    parent.finalizeModule();
  }

  private void assertAttributes(Map<String, Map<String, String>> mergedAttributes, Map<String, String> specifiedAttributes) {
    assertEquals(1, mergedAttributes.size()); // only supports
    Map<String, String> supportsAttributes = mergedAttributes.get(ConfigurationInfo.Supports.KEYWORD);
    assertEquals(ConfigurationInfo.Supports.values().length, supportsAttributes.size());
    for (Map.Entry<String, String> attribute : supportsAttributes.entrySet()) {
      String attributeName = attribute.getKey();
      String attributeValue = attribute.getValue();

      //need to call toUpper() because propertyName is name().toLowerCase()
      ConfigurationInfo.Supports s = ConfigurationInfo.Supports.valueOf(attributeName.toUpperCase());
      String specifiedVal = specifiedAttributes.get(s.getXmlAttributeName());
      if (specifiedVal != null) {
        assertEquals(specifiedVal, attributeValue);
      } else {
        assertEquals(s.getDefaultValue(), attributeValue);
      }
    }
  }

  private void setPrivateField(Object o, String field, Object value) throws Exception{
    Class<?> c = o.getClass();
    Field f = c.getDeclaredField(field);
    f.setAccessible(true);
    f.set(o, value);    }
}
