package io.dropwizard.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.cache.CacheBuilderSpec;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Resources;
import io.dropwizard.jackson.Jackson;
import io.dropwizard.validation.BaseValidator;
import org.assertj.core.data.MapEntry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import javax.validation.Valid;
import javax.validation.Validator;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.io.File;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
public abstract class BaseConfigurationFactoryTest {
private static final String NEWLINE = System.lineSeparator();
@SuppressWarnings("UnusedDeclaration")
public static class ExampleServer {
@JsonProperty
private int port = 8000;
public int getPort() {
return port;
}
public static ExampleServer create(int port) {
ExampleServer server = new ExampleServer();
server.port = port;
return server;
}
}
@SuppressWarnings("UnusedDeclaration")
public static class Example {
@NotNull
@Pattern(regexp = "[\\w]+[\\s]+[\\w]+([\\s][\\w]+)?")
private String name;
@JsonProperty
private int age = 1;
List<String> type;
@JsonProperty
private Map<String, String> properties = new LinkedHashMap<>();
@JsonProperty
private List<ExampleServer> servers = new ArrayList<>();
private boolean admin;
@JsonProperty("my.logger")
private Map<String, String> logger = new LinkedHashMap<>();
public String getName() {
return name;
}
public List<String> getType() {
return type;
}
public Map<String, String> getProperties() {
return properties;
}
public List<ExampleServer> getServers() {
return servers;
}
public boolean isAdmin() {
return admin;
}
public void setAdmin(boolean admin) {
this.admin = admin;
}
public Map<String, String> getLogger() {
return logger;
}
}
static class ExampleWithDefaults {
@NotNull
@Pattern(regexp = "[\\w]+[\\s]+[\\w]+([\\s][\\w]+)?")
@JsonProperty
String name = "Coda Hale";
@JsonProperty
List<String> type = ImmutableList.of("coder", "wizard");
@JsonProperty
Map<String, String> properties = ImmutableMap.of("debug", "true", "settings.enabled", "false");
@JsonProperty
List<ExampleServer> servers = ImmutableList.of(
ExampleServer.create(8080), ExampleServer.create(8081), ExampleServer.create(8082));
@JsonProperty
@Valid
CacheBuilderSpec cacheBuilderSpec = CacheBuilderSpec.disableCaching();
}
static class NonInsatiableExample {
@JsonProperty
String name = "Code Hale";
NonInsatiableExample(@JsonProperty("name") String name) {
this.name = name;
}
}
protected final Validator validator = BaseValidator.newValidator();
protected ConfigurationFactory<Example> factory;
protected File malformedFile;
protected File emptyFile;
protected File invalidFile;
protected File validFile;
protected File typoFile;
protected File wrongTypeFile;
protected File malformedAdvancedFile;
protected static File resourceFileName(String resourceName) throws URISyntaxException {
return new File(Resources.getResource(resourceName).toURI());
}
@After
public void resetConfigOverrides() {
for (Enumeration<?> props = System.getProperties().propertyNames(); props.hasMoreElements();) {
String keyString = (String) props.nextElement();
if (keyString.startsWith("dw.")) {
System.clearProperty(keyString);
}
}
}
@Before
public abstract void setUp() throws Exception;
@Test
public void usesDefaultedCacheBuilderSpec() throws Exception {
final ExampleWithDefaults example =
new YamlConfigurationFactory<>(ExampleWithDefaults.class, validator, Jackson.newObjectMapper(), "dw")
.build();
assertThat(example.cacheBuilderSpec)
.isNotNull();
assertThat(example.cacheBuilderSpec)
.isEqualTo(CacheBuilderSpec.disableCaching());
}
@Test
public void loadsValidConfigFiles() throws Exception {
final Example example = factory.build(validFile);
assertThat(example.getName())
.isEqualTo("Coda Hale");
assertThat(example.getType().get(0))
.isEqualTo("coder");
assertThat(example.getType().get(1))
.isEqualTo("wizard");
assertThat(example.getProperties())
.contains(MapEntry.entry("debug", "true"),
MapEntry.entry("settings.enabled", "false"));
assertThat(example.getServers())
.hasSize(3);
assertThat(example.getServers().get(0).getPort())
.isEqualTo(8080);
}
@Test
public void handlesSimpleOverride() throws Exception {
System.setProperty("dw.name", "Coda Hale Overridden");
final Example example = factory.build(validFile);
assertThat(example.getName())
.isEqualTo("Coda Hale Overridden");
}
@Test
public void handlesExistingOverrideWithPeriod() throws Exception {
System.setProperty("dw.my\\.logger.level", "debug");
final Example example = factory.build(validFile);
assertThat(example.getLogger().get("level"))
.isEqualTo("debug");
}
@Test
public void handlesNewOverrideWithPeriod() throws Exception {
System.setProperty("dw.my\\.logger.com\\.example", "error");
final Example example = factory.build(validFile);
assertThat(example.getLogger().get("com.example"))
.isEqualTo("error");
}
@Test
public void handlesArrayOverride() throws Exception {
System.setProperty("dw.type", "coder,wizard,overridden");
final Example example = factory.build(validFile);
assertThat(example.getType().get(2))
.isEqualTo("overridden");
assertThat(example.getType().size())
.isEqualTo(3);
}
@Test
public void handlesArrayOverrideEscaped() throws Exception {
System.setProperty("dw.type", "coder,wizard,overr\\,idden");
final Example example = factory.build(validFile);
assertThat(example.getType().get(2))
.isEqualTo("overr,idden");
assertThat(example.getType().size())
.isEqualTo(3);
}
@Test
public void handlesSingleElementArrayOverride() throws Exception {
System.setProperty("dw.type", "overridden");
final Example example = factory.build(validFile);
assertThat(example.getType().get(0))
.isEqualTo("overridden");
assertThat(example.getType().size())
.isEqualTo(1);
}
@Test
public void overridesArrayWithIndices() throws Exception {
System.setProperty("dw.type[1]", "overridden");
final Example example = factory.build(validFile);
assertThat(example.getType().get(0))
.isEqualTo("coder");
assertThat(example.getType().get(1))
.isEqualTo("overridden");
}
@Test
public void overridesArrayWithIndicesReverse() throws Exception {
System.setProperty("dw.type[0]", "overridden");
final Example example = factory.build(validFile);
assertThat(example.getType().get(0))
.isEqualTo("overridden");
assertThat(example.getType().get(1))
.isEqualTo("wizard");
}
@Test
public void overridesArrayPropertiesWithIndices() throws Exception {
System.setProperty("dw.servers[0].port", "7000");
System.setProperty("dw.servers[2].port", "9000");
final Example example = factory.build(validFile);
assertThat(example.getServers())
.hasSize(3);
assertThat(example.getServers().get(0).getPort())
.isEqualTo(7000);
assertThat(example.getServers().get(2).getPort())
.isEqualTo(9000);
}
@Test
public void overrideMapProperty() throws Exception {
System.setProperty("dw.properties.settings.enabled", "true");
final Example example = factory.build(validFile);
assertThat(example.getProperties())
.contains(MapEntry.entry("debug", "true"),
MapEntry.entry("settings.enabled", "true"));
}
@Test
public void throwsAnExceptionOnUnexpectedArrayOverride() throws Exception {
System.setProperty("dw.servers.port", "9000");
try {
factory.build(validFile);
failBecauseExceptionWasNotThrown(IllegalArgumentException.class);
} catch (IllegalArgumentException e) {
assertThat(e.getMessage())
.containsOnlyOnce("target is an array but no index specified");
}
}
@Test(expected = ConfigurationParsingException.class)
public void throwsAnExceptionOnArrayOverrideWithInvalidType() throws Exception {
System.setProperty("dw.servers", "one,two");
factory.build(validFile);
failBecauseExceptionWasNotThrown(ConfigurationParsingException.class);
}
@Test
public void throwsAnExceptionOnOverrideArrayIndexOutOfBounds() throws Exception {
System.setProperty("dw.type[2]", "invalid");
try {
factory.build(validFile);
failBecauseExceptionWasNotThrown(ArrayIndexOutOfBoundsException.class);
} catch (ArrayIndexOutOfBoundsException e) {
assertThat(e.getMessage())
.containsOnlyOnce("index is greater than size of array");
}
}
@Test
public void throwsAnExceptionOnOverrideArrayPropertyIndexOutOfBounds() throws Exception {
System.setProperty("dw.servers[4].port", "9000");
try {
factory.build(validFile);
failBecauseExceptionWasNotThrown(ArrayIndexOutOfBoundsException.class);
} catch (ArrayIndexOutOfBoundsException e) {
assertThat(e.getMessage())
.containsOnlyOnce("index is greater than size of array");
}
}
@Test
public void throwsAnExceptionOnMalformedFiles() throws Exception {
factory.build(malformedFile);
failBecauseExceptionWasNotThrown(ConfigurationParsingException.class);
}
@Test
public void throwsAnExceptionOnEmptyFiles() throws Exception {
try {
factory.build(emptyFile);
failBecauseExceptionWasNotThrown(ConfigurationParsingException.class);
} catch (ConfigurationParsingException e) {
assertThat(e.getMessage())
.containsOnlyOnce(" * Configuration at " + emptyFile.toString() + " must not be empty");
}
}
@Test
public void throwsAnExceptionOnInvalidFiles() throws Exception {
try {
factory.build(invalidFile);
failBecauseExceptionWasNotThrown(ConfigurationValidationException.class);
} catch (ConfigurationValidationException e) {
if ("en".equals(Locale.getDefault().getLanguage())) {
assertThat(e.getMessage())
.endsWith(String.format(
"%s has an error:%n" +
" * name must match \"[\\w]+[\\s]+[\\w]+([\\s][\\w]+)?\"%n",
invalidFile.getName()));
}
}
}
@Test
public void handleOverrideDefaultConfiguration() throws Exception {
System.setProperty("dw.name", "Coda Hale Overridden");
System.setProperty("dw.type", "coder,wizard,overridden");
System.setProperty("dw.properties.settings.enabled", "true");
System.setProperty("dw.servers[0].port", "8090");
System.setProperty("dw.servers[2].port", "8092");
final ExampleWithDefaults example =
new YamlConfigurationFactory<>(ExampleWithDefaults.class, validator, Jackson.newObjectMapper(), "dw")
.build();
assertThat(example.name).isEqualTo("Coda Hale Overridden");
assertThat(example.type.get(2)).isEqualTo("overridden");
assertThat(example.type.size()).isEqualTo(3);
assertThat(example.properties).containsEntry("settings.enabled", "true");
assertThat(example.servers.get(0).getPort()).isEqualTo(8090);
assertThat(example.servers.get(2).getPort()).isEqualTo(8092);
}
@Test
public void handleDefaultConfigurationWithoutOverriding() throws Exception {
final ExampleWithDefaults example =
new YamlConfigurationFactory<>(ExampleWithDefaults.class, validator, Jackson.newObjectMapper(), "dw")
.build();
assertThat(example.name).isEqualTo("Coda Hale");
assertThat(example.type).isEqualTo(ImmutableList.of("coder", "wizard"));
assertThat(example.properties).isEqualTo(ImmutableMap.of("debug", "true", "settings.enabled", "false"));
assertThat(example.servers.get(0).getPort()).isEqualTo(8080);
assertThat(example.servers.get(1).getPort()).isEqualTo(8081);
assertThat(example.servers.get(2).getPort()).isEqualTo(8082);
}
@Test
public void throwsAnExceptionIfDefaultConfigurationCantBeInstantiated() throws Exception {
System.setProperty("dw.name", "Coda Hale Overridden");
final YamlConfigurationFactory<NonInsatiableExample> factory =
new YamlConfigurationFactory<>(NonInsatiableExample.class, validator, Jackson.newObjectMapper(), "dw");
assertThatThrownBy(factory::build)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Unable create an instance of the configuration class: " +
"'io.dropwizard.configuration.BaseConfigurationFactoryTest.NonInsatiableExample'");
}
@Test
public void printsDidYouMeanOnUnrecognizedField() throws Exception {
assertThatThrownBy(() -> factory.build(typoFile))
.isInstanceOf(ConfigurationParsingException.class)
.hasMessage(String.format("%s has an error:%n" +
" * Unrecognized field at: propertis%n" +
" Did you mean?:%n" +
" - properties%n" +
" - servers%n" +
" - type%n" +
" - name%n" +
" - age%n" +
" [2 more]%n", typoFile));
}
@Test
public void incorrectTypeIsFound() throws Exception {
assertThatThrownBy(() -> factory.build(wrongTypeFile))
.isInstanceOf(ConfigurationParsingException.class)
.hasMessage(String.format("%s has an error:" + NEWLINE +
" * Incorrect type of value at: age; is of type: String, expected: int" + NEWLINE, wrongTypeFile));
}
@Test
public void printsDetailedInformationOnMalformedContent() throws Exception {
factory.build(malformedAdvancedFile);
}
}