/**
* Copyright (C) 2015 Orange
* Licensed 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 com.francetelecom.clara.cloud.logicalmodel;
import com.francetelecom.clara.cloud.commons.BusinessException;
import com.francetelecom.clara.cloud.logicalmodel.InvalidConfigServiceException.ErrorType;
import com.francetelecom.clara.cloud.logicalmodel.samplecatalog.ConfigLogicalModelCatalog;
import com.francetelecom.clara.cloud.logicalmodel.samplecatalog.SpringooLogicalModelCatalog;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.lang3.StringUtils;
import org.fest.assertions.Assertions;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.transaction.Transactional;
import java.util.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Tests the LogicalConfigService parsing and consistency checking.<br>
* TODO: find a way to simulate invalid unicode characters easily. Get some inspiration in https://issues.apache.org/jira/browse/LANG-100 ?<br>
* @author skwg9735
*/
@ContextConfiguration(locations = "application-context.xml")
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class LogicalConfigServiceTest {
public static final LogicalConfigServiceUtils LOGICAL_CONFIG_SERVICE_UTILS = new LogicalConfigServiceUtils();
@Autowired
SpringooLogicalModelCatalog utilSpringooIntegration;
@Autowired
ConfigLogicalModelCatalog configLogicalModelCatalog;
@Autowired
LogicalDeploymentRepository logicalDeploymentRepository;
//syntaxic sugar
private HashSet<String> IGNORED_DUPLICATES;
private StringBuffer IGNORE_COLLISIONS;
public LogicalConfigServiceTest() {
}
@Before
public void setUp() throws Exception {
IGNORED_DUPLICATES = new HashSet<String>();
IGNORE_COLLISIONS = new StringBuffer();
}
/**
* Tests that the long string content properly serializes.
* TODO: test with postgresql in addition to default hsqldb
* @throws BusinessException
*/
@Test
@DirtiesContext
@Transactional
public void testPersistenceOfLongConfigSetContent() throws BusinessException {
createLongConfigSetContent(LogicalConfigService.MAX_CONFIG_SET_CHARS -1, true);
}
@Test
public void testConfigSetTooLargeSizeDetected() throws BusinessException {
try {
createLongConfigSetContent(LogicalConfigService.MAX_CONFIG_SET_CHARS + 1, false);
Assert.assertFalse("Should have failed", true);
} catch (InvalidConfigServiceException e) {
Assert.assertEquals(ErrorType.TOO_LONG, e.getType());
Assert.assertEquals(LogicalConfigService.MAX_CONFIG_SET_CHARS, e.getMaxLength());
}
}
private void createLongConfigSetContent(int size, boolean testPersistence) throws BusinessException {
String frontEndConfigSetContent = "#Comment too long ?";
frontEndConfigSetContent = StringUtils.rightPad(frontEndConfigSetContent, size, '?');
createConfigSetLogicalModelCatalog(frontEndConfigSetContent, testPersistence);
}
@Test
public void testDuplicateKeysInSingleConfigSetDetected() throws BusinessException {
try {
String frontEndConfigSetContent = "Key1=value1\n" + "Key1=value2\n";
LogicalDeployment ld = configLogicalModelCatalog.createLogicalModel("TestInvalidUnicodeCharacters");
Set<LogicalConfigService> frontEndConfigService = ld.listLogicalServices(LogicalConfigService.class, ConfigLogicalModelCatalog.FRONT_END_CONFIG);
assert frontEndConfigService.size() ==1;
LogicalConfigService configService = frontEndConfigService.iterator().next();
configService.setConfigSetContent(frontEndConfigSetContent);
configService.checkForDuplicatesWithinService();
Assert.fail("expected exception to be caught");
} catch (InvalidConfigServiceException e) {
Assert.assertEquals(ErrorType.DUPLICATE_KEYS, e.getType());
Assert.assertEquals(1, e.getDuplicateKeys().size());
Assert.assertEquals("Key1", e.getDuplicateKeys().iterator().next());
}
}
/**
* Checks that duplicates among configsets are detected, but that the full list of properties can still
* be displayed to help end-users diagnose these duplicates
* @throws BusinessException
*/
@Test
public void testDuplicateKeysAmongConfigSetsDetectedAndDisplayed() throws BusinessException {
String frontEndConfigSetContent = "Key1=value1\n" + "Key2=value2\n";
String backendConfigSetContent = "";
String wholeAppConfigSetContent = "Key2=value1\n" + "Key3=value2\n";
LogicalDeployment ld = configLogicalModelCatalog.createLogicalModel("TestInvalidUnicodeCharacters");
Set<LogicalConfigService> backConfig = ld.listLogicalServices(LogicalConfigService.class, ConfigLogicalModelCatalog.BACK_END_CONFIG);
assert backConfig.size() ==1;
backConfig.iterator().next().setConfigSetContent(backendConfigSetContent);
Set<LogicalConfigService> frontConfig = ld.listLogicalServices(LogicalConfigService.class, ConfigLogicalModelCatalog.FRONT_END_CONFIG);
assert frontConfig.size() ==1;
frontConfig.iterator().next().setConfigSetContent(frontEndConfigSetContent);
Set<LogicalConfigService> wholeConfig = ld.listLogicalServices(LogicalConfigService.class, ConfigLogicalModelCatalog.WHOLE_APP_CONFIG);
assert wholeConfig.size() ==1;
wholeConfig.iterator().next().setConfigSetContent(wholeAppConfigSetContent);
try {
ld.checkOverallConsistency();
Assert.assertFalse("Should have failed", true);
} catch (LogicalModelNotConsistentException ie) {
boolean foundMatchingConfigException = false;
for (BusinessException businessException : ie.getErrors()) {
if (businessException instanceof InvalidConfigServiceException) {
InvalidConfigServiceException e = (InvalidConfigServiceException) businessException;
Assert.assertEquals(ErrorType.DUPLICATE_KEYS, e.getType());
Assert.assertEquals(1, e.getDuplicateKeys().size());
Assert.assertEquals("configKey2", e.getDuplicateKeys().iterator().next());
foundMatchingConfigException = true;
}
}
assertTrue("Did not catch an InvalidConfigServiceException", foundMatchingConfigException);
}
//Test how the UI can display these duplicated keys to report them
StringBuilder sb = new StringBuilder();
List<ProcessingNode> processingNodes = ld.listProcessingNodes();
for (ProcessingNode processingNode : processingNodes) {
Map<String,String> mergedProperties = new HashMap<String,String>();
String executionNodeLabel = processingNode.getLabel();
List<LogicalConfigService> logicalConfigServices = processingNode.listLogicalServices(LogicalConfigService.class);
for (LogicalConfigService logicalConfigService : logicalConfigServices) {
LogicalConfigServiceUtils.StructuredLogicalConfigServiceContent structuredLogicalConfigServiceContent = LOGICAL_CONFIG_SERVICE_UTILS.parseConfigContent(logicalConfigService.getConfigSetContent());
for (LogicalConfigServiceUtils.ConfigEntry configEntry : structuredLogicalConfigServiceContent.getConfigEntries()) {
String previousValue = mergedProperties.put(configEntry.getKey(), configEntry.getValue());
sb.append("JEEProcess=" + executionNodeLabel + ", ConfigSet=" + logicalConfigService.getLabel() + ", key=" + logicalConfigService.getKeyPrefix() + configEntry.getKey() + ", value=" + configEntry.getValue() + ", isDuplicate=" + (previousValue != null) + ", comment=" + configEntry.getComment() + "\n");
}
}
}
String conf = sb.toString();
Assertions.assertThat(conf).contains("JEEProcess=BackEnd, ConfigSet=WholeAppConfig, key=configKey2, value=value1, isDuplicate=false, comment=null");
Assertions.assertThat(conf).contains("JEEProcess=BackEnd, ConfigSet=WholeAppConfig, key=configKey3, value=value2, isDuplicate=false, comment=null");
Assertions.assertThat(conf).contains("JEEProcess=FrontEnd, ConfigSet=FrontEndConfig, key=configKey1, value=value1, isDuplicate=false, comment=null");
Assertions.assertThat(conf).contains("JEEProcess=FrontEnd, ConfigSet=FrontEndConfig, key=configKey2, value=value2, isDuplicate=false, comment=null");
Assertions.assertThat(conf).contains("JEEProcess=FrontEnd, ConfigSet=WholeAppConfig, key=configKey2, value=value1, isDuplicate=true, comment=null");
Assertions.assertThat(conf).contains("JEEProcess=FrontEnd, ConfigSet=WholeAppConfig, key=configKey3, value=value2, isDuplicate=false, comment=null");
}
@Test
public void testTooManyKeysConfigSetsDetected() throws BusinessException {
try {
StringBuffer sb = new StringBuffer();
for (int keyIndex = 0; keyIndex < ProcessingNode.MAX_CONFIG_SET_ENTRIES_PER_EXEC_NODE + 1; keyIndex++) {
sb.append("key" + keyIndex + "=" + "value" + keyIndex + "\n");
}
String frontEndConfigSetContent = sb.toString();
createConfigSetLogicalModelCatalog(frontEndConfigSetContent, false);
Assert.assertFalse("Should have failed", true);
} catch (LogicalModelNotConsistentException ie) {
boolean foundMatchingConfigException = false;
for (BusinessException businessException : ie.getErrors()) {
if (businessException instanceof InvalidConfigServiceException) {
InvalidConfigServiceException e = (InvalidConfigServiceException) businessException;
Assert.assertEquals(ErrorType.TOO_MANY_ENTRIES, e.getType());
Assert.assertEquals(ProcessingNode.MAX_CONFIG_SET_ENTRIES_PER_EXEC_NODE, e.getMaxEntryCount());
Assert.assertEquals(ProcessingNode.MAX_CONFIG_SET_ENTRIES_PER_EXEC_NODE + 3, e.getEntryCount());
foundMatchingConfigException = true;
}
}
assertTrue("Did not catch an InvalidConfigServiceException", foundMatchingConfigException);
}
}
@Test
public void testEmptyKeyPrefix() throws BusinessException {
LogicalConfigService lcs = createConfigSetLogicalModelCatalog("toto=titi", false, "");
Properties mergedProperties = new Properties();
lcs.mergeAndCheckForDuplicateKeys(mergedProperties, IGNORED_DUPLICATES, IGNORE_COLLISIONS);
Assert.assertNotNull(mergedProperties.getProperty("toto"));
Assert.assertEquals(mergedProperties.getProperty("toto"), "titi");
}
@Test()
public void empty_key_name_should_be_supported() throws BusinessException {
LogicalConfigService lcs = createConfigSetLogicalModelCatalog("#comment\n=some value", false, "prefix.");
Properties mergedProperties = new Properties();
lcs.mergeAndCheckForDuplicateKeys(mergedProperties, IGNORED_DUPLICATES, IGNORE_COLLISIONS);
Assert.assertEquals("expected empty key to return specified value", "some value", mergedProperties.getProperty("prefix."));
}
@Test()
public void empty_key_name_should_be_supported_without_prefix() throws BusinessException {
LogicalConfigService lcs = createConfigSetLogicalModelCatalog("#comment\n=some value", false, "");
Properties mergedProperties = new Properties();
lcs.mergeAndCheckForDuplicateKeys(mergedProperties, IGNORED_DUPLICATES, IGNORE_COLLISIONS);
Assert.assertEquals("expected empty key to return specified value", "some value", mergedProperties.getProperty(""));
}
@Test
public void entries_with_empty_values_are_supported() throws BusinessException {
LogicalConfigService lcs = createConfigSetLogicalModelCatalog("#comment\nkeyWithEmptyValue=", false, "prefix.");
Properties mergedProperties = new Properties();
lcs.mergeAndCheckForDuplicateKeys(mergedProperties, IGNORED_DUPLICATES, IGNORE_COLLISIONS);
Assert.assertTrue("expected keyWithEmptyValue to be present in list of keys", mergedProperties.containsKey("prefix.keyWithEmptyValue"));
Assert.assertEquals("expected empty value to return empty string", "", mergedProperties.getProperty("prefix.keyWithEmptyValue"));
}
@Test
public void testConfigServiceUtils() throws ConfigurationException, InvalidConfigServiceException {
//given
String header =
"#HeaderComment1\n" +
"#HeaderComment2";
String propertiesContent = header + "\n\n" + "#Comment1.1\n" + "Key1=value1\n" + "# Comment2 Line1\n" + "# Comment2 Line2\n" + "Key2=value2\n";
//when
LogicalConfigServiceUtils.StructuredLogicalConfigServiceContent parsedContent = LOGICAL_CONFIG_SERVICE_UTILS.parseConfigContent(propertiesContent);
//then
List<LogicalConfigServiceUtils.ConfigEntry> expectedEntries = new ArrayList<LogicalConfigServiceUtils.ConfigEntry>();
expectedEntries.add(new LogicalConfigServiceUtils.ConfigEntry("Key1", "value1", "Comment1.1"));
expectedEntries.add(new LogicalConfigServiceUtils.ConfigEntry("Key2", "value2", " Comment2 Line1\n Comment2 Line2"));
LogicalConfigServiceUtils.StructuredLogicalConfigServiceContent expectedContent = new LogicalConfigServiceUtils.StructuredLogicalConfigServiceContent(
header, expectedEntries);
assertEquals(expectedContent, parsedContent);
// Then dump back
//when
String dumpedProperties = LOGICAL_CONFIG_SERVICE_UTILS.dumpConfigContentToString(expectedContent);
//Then: dumped content normalizes comments with a prefix space if missing (i.e. not strictly symetric)
String expectedNormalizedDumpedContent =
"#HeaderComment1\n" +
"#HeaderComment2\n" +
"\n" +
"# Comment1.1\n" +
"Key1=value1\n" +
"# Comment2 Line1\n" +
"# Comment2 Line2\n" +
"Key2=value2\n";
assertEquals(expectedNormalizedDumpedContent, dumpedProperties);
}
@Test
public void parses_comments_received_from_apache_commons() {
//removes pound as prefix
assertCommentEscaped("comment", "#comment");
assertCommentEscaped("comment1\ncomment2", "#comment1\n#comment2");
assertCommentEscaped("comment1\n\rcomment2", "#comment1\n\r#comment2");
assertCommentEscaped("comment1 with # embedded in between\n\rcomment2", "#comment1 with # embedded in between\n\r#comment2");
}
private void assertCommentEscaped(String expectedEscape, String rawComment) {
assertEquals(expectedEscape, LOGICAL_CONFIG_SERVICE_UTILS.escapesPoundsInComments(rawComment));
}
@Test
public void testConfigServiceUtils_when_no_header_comments() throws ConfigurationException, InvalidConfigServiceException {
String propertiesContent = "#Comment1.1\n" + "Key1=value1\n";
LogicalConfigServiceUtils.StructuredLogicalConfigServiceContent parsedContent = LOGICAL_CONFIG_SERVICE_UTILS.parseConfigContent(propertiesContent);
List<LogicalConfigServiceUtils.ConfigEntry> expectedEntries = new ArrayList<LogicalConfigServiceUtils.ConfigEntry>();
expectedEntries.add(new LogicalConfigServiceUtils.ConfigEntry("Key1", "value1", "Comment1.1"));
LogicalConfigServiceUtils.StructuredLogicalConfigServiceContent expectedContent = new LogicalConfigServiceUtils.StructuredLogicalConfigServiceContent(
null, expectedEntries);
assertEquals(expectedContent, parsedContent);
}
private LogicalConfigService createConfigSetLogicalModelCatalog(String frontEndConfigSetContent, boolean testPersistence) throws InvalidConfigServiceException, LogicalModelNotConsistentException {
return createConfigSetLogicalModelCatalog(frontEndConfigSetContent, testPersistence, "");
}
private LogicalConfigService createConfigSetLogicalModelCatalog(String frontEndConfigSetContent, boolean testPersistence, String keyPrefix) throws InvalidConfigServiceException, LogicalModelNotConsistentException {
LogicalDeployment ld = configLogicalModelCatalog.createLogicalModel("TestInvalidUnicodeCharacters");
Set<LogicalConfigService> config = ld.listLogicalServices(LogicalConfigService.class, ConfigLogicalModelCatalog.FRONT_END_CONFIG);
assert config.size() ==1;
LogicalConfigService lcs = config.iterator().next();
lcs.setConfigSetContent(frontEndConfigSetContent);
lcs.setKeyPrefix(keyPrefix);
if (testPersistence) {
logicalDeploymentRepository.save(ld);
LogicalDeployment reloadedLd = logicalDeploymentRepository.findOne(ld.getId());
assertEquals(ld, reloadedLd);
}
ld.checkOverallConsistency();
return lcs;
}
@Test(expected=IllegalArgumentException.class)
public void keyPrefix_cannot_be_null() throws BusinessException {
LogicalConfigService lcs = createConfigSetLogicalModelCatalog("toto=titi", false, null);
lcs.setKeyPrefix(null);
}
}