diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ace89a032..07d195dc0 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -102,6 +102,8 @@ jobs:
- name: Run browsers ITs
shell: bash
run: |
+ export SPECTRUM_VIDEO_SKIPDUPLICATEFRAMES=false
+ export SPECTRUM_APPLICATION_BASEURL='https://the-internet.herokuapp.com/'
./mvnw install:install-file -ntp -Dfile=$GITHUB_WORKSPACE/spectrum-${{ env.VERSION }}.jar -DgroupId=io.github.giulong -DartifactId=spectrum -Dversion=${{ env.VERSION }} -Dpackaging=jar
./mvnw install -DskipSign -Dmaven.plugin.validation=NONE ${{ matrix.directives }} -ntp -P ${{ matrix.profiles }}
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index eb12451b6..c95ce3134 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -3,6 +3,7 @@ name: CodeQL
on:
push:
branches: [ "develop", "feature/**", "bugfix/**" ]
+ paths-ignore: [ '.mvn/**', 'mvnw', 'mvnw.cmd', '.run/**', 'docs/**', '.editorconfig', '.gitignore', '**.md' ]
jobs:
analyze:
diff --git a/.run/spectrum framework-only no tests.run.xml b/.run/spectrum framework-only no tests.run.xml
new file mode 100644
index 000000000..35b5d922c
--- /dev/null
+++ b/.run/spectrum framework-only no tests.run.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.run/spectrum framework-only.run.xml b/.run/spectrum framework-only.run.xml
index 61983155b..f474ce7f6 100644
--- a/.run/spectrum framework-only.run.xml
+++ b/.run/spectrum framework-only.run.xml
@@ -12,7 +12,6 @@
-
diff --git a/docs/README.md b/docs/README.md
index 51ff56516..437ebfae1 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -622,8 +622,55 @@ runtime:
## Values interpolation
-Plain values (not objects nor arrays) in `configuration*.yaml` and `data*.yaml` can be interpolated with a dollar-string
-in one of these two ways, depending on the type needed as result. Let's suppose we have the variable `key = 123`:
+Plain values (not objects nor arrays) in `configuration*.yaml` and `data*.yaml` can be interpolated, meaning their value can be injected
+instead of being hardcoded. This is quite useful if you want to keep the same configuration files, while being able to tweak it at runtime, based
+on the execution environment for instance.
+
+There are a few ways to interpolate configuration keys:
+
+* [In-place interpolation](#in-place-interpolation): directly in the `configuration.yaml`
+* [Environment variables interpolation](#environment-variables-interpolation): injecting values from the current env
+* [System properties interpolation](#system-properties-interpolation): injecting values passed as system properties
+
+> 💡 **Tip**
+> You can mix all the available options, for example having some keys interpolated in-place, and some others injected from env vars and system properties.
+
+Each interpolator can be configured in the base `configuration.yaml` under the `config` node. Here you can see the internal
+[configuration.default.yaml]({{ site.repository_url }}/spectrum/src/main/resources/yaml/configuration.default.yaml){:target="_blank"}:
+
+{% include copyCode.html %}
+
+```yaml
+# Generic configuration. This node is read only from the base configuration.yaml
+config:
+ interpolators: # Configuration keys interpolators
+ environment: # Environment variables interpolator
+ priority: 0 # Sets the order of evaluation of this interpolator among others. Higher priority wins.
+ prefix: spectrum # Variable prefix
+ delimiter: . # Variable tokens' delimiter
+ transformCase: none # Function to specify how to transform the original camelCase of the key to match the external variable to search
+ properties: # Properties interpolator
+ priority: 1 # Sets the order of evaluation of this interpolator among others. Higher priority wins.
+ prefix: spectrum # Variable prefix
+ delimiter: . # Variable tokens' delimiter
+ transformCase: none # Function to specify how to transform the original camelCase of the key to match the external variable to search
+ inPlace: # In-place configuration file interpolator
+ priority: 2 # Sets the order of evaluation of this interpolator among others. Higher priority wins.
+ enabled: true
+```
+
+You can see that each interpolator has a `priority`. That's an int value that specifies the resolution order:
+when a key is resolved by more than one interpolator, the one with the highest priority is used to inject the interpolated value.
+The other keys are explained in the corresponding sections.
+
+> ⚠️ **Overriding the `config` node**
+> You can customize the `config` node in the base `configuration.yaml` only. Overriding it in any other `configuration-.yaml`
+> simply won't have any effect. This is needed since the `config` node is a *meta*-configuration that specifies how to read all the other keys.
+
+### In-place Interpolation
+
+You can interpolate values directly in the `configuration*.yaml` and `data*.yaml` with a dollar-string in one of the following two ways,
+depending on the type needed as result. Let's suppose we have the variable `key = 123`:
| Needed type | Interpolation key | Result | Behaviour if not found |
|-------------|-------------------|--------|--------------------------------------------------------------|
@@ -746,7 +793,7 @@ runtime:
profiles: ${active-profiles:-local}
```
-These are the variables already available in the [configuration.default.yaml]({{ site.repository_url }}/spectrum/src/main/resources/yaml/configuration.default.yaml){:
+These variables are already available in the [configuration.default.yaml]({{ site.repository_url }}/spectrum/src/main/resources/yaml/configuration.default.yaml){:
target="_blank"}.
You can add your own and even override the default ones in your `configuration*.yaml`:
@@ -754,10 +801,137 @@ You can add your own and even override the default ones in your `configuration*.
|----------------------|------------------------------|------------------------------|
| spectrum.profiles | local | local |
| spectrum.driver | chrome | chrome |
+| spectrum.environment | local | local |
| downloadsFolder | ${user.dir}\target\downloads | ${user.dir}/target/downloads |
| summaryReportOutput | target/spectrum/summary | target/spectrum/summary |
| testBookReportOutput | target/spectrum/testbook | target/spectrum/testbook |
+The in-place interpolator is enabled by default with the highest priority. You can disable it or change its priority with this configuration snippet:
+
+{% include copyCode.html %}
+
+```yaml
+config:
+ interpolators:
+ inPlace:
+ priority: 123
+ enabled: false
+```
+
+### Environment Variables Interpolation
+
+You can **avoid specifying keys directly in the yaml files** and interpolate values taken from environment variables
+by providing this configuration snippet:
+
+{% include copyCode.html %}
+
+```yaml
+config:
+ interpolators:
+ environment: { }
+```
+
+Providing an empty object as above, you'll leverage on the internal defaults, which are the following:
+
+{% include copyCode.html %}
+
+```yaml
+config:
+ interpolators:
+ environment:
+ priority: 0
+ prefix: spectrum
+ delimiter: .
+ transformCase: none
+```
+
+This means every configuration key will be searched in env vars, with the `spectrum` prefix and words delimited by a dot.
+For instance, you can inject the `application.baseUrl` setting an env variable named `spectrum.application.baseUrl`.
+
+To give you another example, you can use this config to inject env vars like `APPLICATION_BASEURL`:
+
+{% include copyCode.html %}
+
+```yaml
+config:
+ interpolators:
+ environment:
+ prefix: ''
+ delimiter: _
+ transformCase: upper
+```
+
+Allowed values for the `transformCase` property are:
+
+| Value | Description | Example |
+|---------|-----------------------------------------------------------------------------------|---------------------|
+| `none` | searches a key with the same case of the property, which is `camelCase` (default) | application.baseUrl |
+| `lower` | searches a lowercase key | application.baseurl |
+| `upper` | searches a uppercase key | APPLICATION.BASEURL |
+
+> ⚠️ **Priority**
+> Pay attention to the priority: by default, the `inPlace` interpolator takes precedence over this one.
+> This means that if you provide the same key in the yaml file, the env var will be ignored. You have to options to inject the env var:
+> * delete the hardcoded key
+> * set a higher priority in the environment interpolator
+
+### System Properties Interpolation
+
+You can **avoid specifying keys directly in the yaml files** and interpolate values taken from system properties
+by providing this configuration snippet:
+
+{% include copyCode.html %}
+
+```yaml
+config:
+ interpolators:
+ properties: { }
+```
+
+Providing an empty object as above, you'll leverage on the internal defaults, which are the following:
+
+{% include copyCode.html %}
+
+```yaml
+config:
+ interpolators:
+ properties:
+ priority: 1
+ prefix: spectrum
+ delimiter: .
+ transformCase: none
+```
+
+This means every configuration key will be searched in system properties, with the `spectrum` prefix and words delimited by a dot.
+For instance, you can inject the `application.baseUrl` setting the system property `-Dspectrum.application.baseUrl`.
+
+To give you another example, you can use this config to inject system properties like `-DAPPLICATION_BASEURL`:
+
+{% include copyCode.html %}
+
+```yaml
+config:
+ interpolators:
+ properties:
+ prefix: ''
+ delimiter: _
+ transformCase: upper
+```
+
+Allowed values for the `transformCase` property are:
+
+| Value | Description | Example |
+|---------|-----------------------------------------------------------------------------------|---------------------|
+| `none` | searches a key with the same case of the property, which is `camelCase` (default) | application.baseUrl |
+| `lower` | searches a lowercase key | application.baseurl |
+| `upper` | searches a uppercase key | APPLICATION.BASEURL |
+
+> ⚠️ **Priority**
+> Pay attention to the priority: by default, the `inPlace` interpolator takes precedence over this one.
+> This means that if you provide the same key in the yaml file, the system property will be ignored. You have to options to inject the system property:
+> * delete the hardcoded key
+> * set a higher priority in the properties interpolator
+
---
## Configuring the Driver
diff --git a/it-windows/pom.xml b/it-windows/pom.xml
index f15f50dfe..b55804fa4 100644
--- a/it-windows/pom.xml
+++ b/it-windows/pom.xml
@@ -59,7 +59,7 @@
- windows
+ chrome
windowsTests
diff --git a/it/pom.xml b/it/pom.xml
index 1ca76bd7c..f16d69ee5 100644
--- a/it/pom.xml
+++ b/it/pom.xml
@@ -53,6 +53,15 @@
org.apache.maven.plugins
maven-failsafe-plugin
+
+
+ false
+ https://the-internet.herokuapp.com/
+
+
+ overridden by the env variable above
+
+
diff --git a/it/src/test/resources/configuration.yaml b/it/src/test/resources/configuration.yaml
index 49d6aa900..55a8dfed6 100644
--- a/it/src/test/resources/configuration.yaml
+++ b/it/src/test/resources/configuration.yaml
@@ -1,5 +1,13 @@
+config:
+ interpolators:
+ environment:
+ priority: 10
+ delimiter: _
+ transformCase: upper
+ properties: { }
+
application:
- baseUrl: https://the-internet.herokuapp.com/
+ baseUrl: set as env var in pom.xml. Environment interpolator has a higher priority (see above)
drivers:
waits:
@@ -18,7 +26,7 @@ video:
frames:
- autoAfter
- manual
- skipDuplicateFrames: false
+ # skipDuplicateFrames: false set as env variable in pom.xml
extent:
theme: DARK
diff --git a/pom.xml b/pom.xml
index a68733b58..6a3b9a7a4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -52,7 +52,7 @@
8.12.6
4.38.0
2.0.17
- 4.38.0
+ 4.39.0
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumSessionListener.java b/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumSessionListener.java
index a2ef02b6e..cbe69ef4f 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumSessionListener.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/SpectrumSessionListener.java
@@ -6,6 +6,7 @@
import io.github.giulong.spectrum.types.ProjectProperties;
import io.github.giulong.spectrum.utils.*;
+import io.github.giulong.spectrum.utils.Configuration.Config;
import io.github.giulong.spectrum.utils.events.EventsDispatcher;
import lombok.extern.slf4j.Slf4j;
@@ -21,6 +22,7 @@ public class SpectrumSessionListener implements LauncherSessionListener {
public static final String DEFAULT_CONFIGURATION_UNIX_YAML = "yaml/configuration.default.unix.yaml";
public static final String CONFIGURATION = "configuration";
public static final String PROFILE_NODE = "/runtime/profiles";
+ public static final String CONFIG_NODE = "/config";
public static final String VARS_NODE = "/vars";
private final Vars vars = Vars.getInstance();
@@ -41,6 +43,7 @@ public void launcherSessionOpened(final LauncherSession session) {
final ProjectProperties projectProperties = yamlUtils.readInternal("properties.yaml", ProjectProperties.class);
log.info(freeMarkerWrapper.interpolate(fileUtils.read("banner.txt"), projectProperties));
+ parseConfig();
parseConfiguration();
session.getLauncher().registerTestExecutionListeners(configuration.getSummary().getSummaryGeneratingListener());
@@ -64,6 +67,17 @@ public void launcherSessionClosed(final LauncherSession session) {
eventsDispatcher.sessionClosed();
}
+ void parseConfig() {
+ final Config config = yamlUtils.readInternalNode(CONFIG_NODE, DEFAULT_CONFIGURATION_YAML);
+
+ if (isUnix()) {
+ yamlUtils.updateWithInternalNode(config, CONFIG_NODE, DEFAULT_CONFIGURATION_UNIX_YAML);
+ }
+
+ yamlUtils.updateWithClientNode(config, CONFIG_NODE, CONFIGURATION);
+ configuration.setConfig(config);
+ }
+
void parseConfiguration() {
final List profileConfigurations = parseProfiles()
.stream()
@@ -71,6 +85,7 @@ void parseConfiguration() {
.toList();
parseVars(profileConfigurations);
+
yamlUtils.updateWithInternalFile(configuration, DEFAULT_CONFIGURATION_YAML);
if (isUnix()) {
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/DriverDeserializer.java b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/DriverDeserializer.java
similarity index 83%
rename from spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/DriverDeserializer.java
rename to spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/DriverDeserializer.java
index f45651ee1..0d6f68e08 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/DriverDeserializer.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/DriverDeserializer.java
@@ -1,4 +1,4 @@
-package io.github.giulong.spectrum.internals.jackson.deserializers;
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation;
import static lombok.AccessLevel.PRIVATE;
@@ -10,9 +10,7 @@
import io.github.giulong.spectrum.drivers.*;
import lombok.NoArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-@Slf4j
@NoArgsConstructor(access = PRIVATE)
public class DriverDeserializer extends InterpolatedDeserializer> {
@@ -24,9 +22,7 @@ public static DriverDeserializer getInstance() {
@Override
public Driver, ?, ?> deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException {
- final String value = jsonParser.getValueAsString();
- final String interpolatedValue = interpolate(value, jsonParser.currentName());
- log.trace("Deserializing driver from value {} -> {}", value, interpolatedValue);
+ final String interpolatedValue = interpolate(jsonParser);
return switch (interpolatedValue) {
case "chrome" -> new Chrome();
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/EnvironmentDeserializer.java b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/EnvironmentDeserializer.java
similarity index 82%
rename from spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/EnvironmentDeserializer.java
rename to spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/EnvironmentDeserializer.java
index 76242991a..812a3088f 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/EnvironmentDeserializer.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/EnvironmentDeserializer.java
@@ -1,4 +1,4 @@
-package io.github.giulong.spectrum.internals.jackson.deserializers;
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation;
import static lombok.AccessLevel.PRIVATE;
@@ -13,9 +13,7 @@
import io.github.giulong.spectrum.utils.environments.LocalEnvironment;
import lombok.NoArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-@Slf4j
@NoArgsConstructor(access = PRIVATE)
public class EnvironmentDeserializer extends InterpolatedDeserializer {
@@ -27,9 +25,7 @@ public static EnvironmentDeserializer getInstance() {
@Override
public Environment deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException {
- final String value = jsonParser.getValueAsString();
- final String interpolatedValue = interpolate(value, jsonParser.currentName());
- log.trace("Deserializing environment from value {} -> {}", value, interpolatedValue);
+ final String interpolatedValue = interpolate(jsonParser);
return switch (interpolatedValue) {
case "local" -> new LocalEnvironment();
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedBooleanDeserializer.java b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedBooleanDeserializer.java
similarity index 75%
rename from spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedBooleanDeserializer.java
rename to spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedBooleanDeserializer.java
index f07dda87b..24a8a7960 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedBooleanDeserializer.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedBooleanDeserializer.java
@@ -1,4 +1,4 @@
-package io.github.giulong.spectrum.internals.jackson.deserializers;
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation;
import static lombok.AccessLevel.PRIVATE;
@@ -8,9 +8,7 @@
import com.fasterxml.jackson.databind.DeserializationContext;
import lombok.NoArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-@Slf4j
@NoArgsConstructor(access = PRIVATE)
public class InterpolatedBooleanDeserializer extends InterpolatedDeserializer {
@@ -22,9 +20,6 @@ public static InterpolatedBooleanDeserializer getInstance() {
@Override
public Boolean deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException {
- final String value = jsonParser.getValueAsString();
- log.trace("Deserializing Boolean from value {}", value);
-
- return Boolean.parseBoolean(interpolate(value, jsonParser.currentName()));
+ return Boolean.parseBoolean(interpolate(jsonParser));
}
}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedDeserializer.java b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedDeserializer.java
new file mode 100644
index 000000000..c9dfe76c3
--- /dev/null
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedDeserializer.java
@@ -0,0 +1,53 @@
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation;
+
+import static java.util.Comparator.comparing;
+
+import java.util.List;
+import java.util.Optional;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+
+import io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators.Interpolator;
+import io.github.giulong.spectrum.utils.Configuration;
+import io.github.giulong.spectrum.utils.Configuration.Config;
+import io.github.giulong.spectrum.utils.Reflections;
+
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public abstract class InterpolatedDeserializer extends JsonDeserializer {
+
+ private final Configuration configuration = Configuration.getInstance();
+
+ @SneakyThrows
+ public String interpolate(final String value, final JsonParser jsonParser) {
+ final Config config = configuration.getConfig();
+ final String currentName = jsonParser.currentName();
+ log.trace("{} is deserializing {}: {}", getClass().getSimpleName(), currentName, value);
+
+ if (config != null) {
+ final List interpolators = Reflections.getFieldsValueOf(config.getInterpolators());
+ final String interpolatedValue = interpolators
+ .stream()
+ .filter(Interpolator::isEnabled)
+ .sorted(comparing(Interpolator::getPriority).reversed())
+ .map(i -> i.findVariableFor(value, jsonParser))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .findFirst()
+ .orElse(value);
+
+ log.debug("Chosen value: {} -> {}", currentName, interpolatedValue);
+ return interpolatedValue;
+ }
+
+ return value;
+ }
+
+ @SneakyThrows
+ public String interpolate(final JsonParser jsonParser) {
+ return interpolate(jsonParser.getValueAsString(), jsonParser);
+ }
+}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedObjectDeserializer.java b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedObjectDeserializer.java
similarity index 84%
rename from spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedObjectDeserializer.java
rename to spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedObjectDeserializer.java
index 38b8abdd1..4007dfb00 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedObjectDeserializer.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedObjectDeserializer.java
@@ -1,4 +1,4 @@
-package io.github.giulong.spectrum.internals.jackson.deserializers;
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation;
import static java.util.stream.Collectors.toMap;
import static lombok.AccessLevel.PRIVATE;
@@ -20,6 +20,7 @@
import io.github.giulong.spectrum.utils.Vars;
import lombok.NoArgsConstructor;
+import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@@ -48,31 +49,33 @@ public Object deserialize(final JsonParser jsonParser, final DeserializationCont
return switch (jsonNodeType) {
case NUMBER -> jsonNode.numberValue();
case BOOLEAN -> jsonNode.booleanValue();
- case STRING -> traverse(jsonNode.textValue(), currentName);
- case OBJECT -> traverse(objectMapper.convertValue(jsonNode, Map.class), currentName);
- case ARRAY -> traverse(objectMapper.convertValue(jsonNode, List.class), currentName);
- default -> traverse(jsonNode, currentName);
+ case STRING -> traverse(jsonNode.textValue(), jsonParser);
+ case OBJECT -> traverse(objectMapper.convertValue(jsonNode, Map.class), jsonParser);
+ case ARRAY -> traverse(objectMapper.convertValue(jsonNode, List.class), jsonParser);
+ default -> traverse(jsonNode, jsonParser);
};
}
- Object traverse(final Object value, final String currentName) {
+ Object traverse(final Object value, final JsonParser jsonParser) {
return switch (value) {
case String v -> {
final Matcher matcher = INT_PATTERN.matcher(v);
yield matcher.matches()
- ? interpolate(v, currentName, matcher)
- : InterpolatedStringDeserializer.getInstance().interpolate(v, currentName);
+ ? interpolate(v, jsonParser, matcher)
+ : InterpolatedStringDeserializer.getInstance().interpolate(v, jsonParser);
}
case Map, ?> m -> m
.entrySet()
.stream()
- .collect(toMap(Map.Entry::getKey, e -> traverse(e.getValue(), currentName)));
- case List> l -> l.stream().map(e -> traverse(e, currentName)).toList();
+ .collect(toMap(Map.Entry::getKey, e -> traverse(e.getValue(), jsonParser)));
+ case List> l -> l.stream().map(e -> traverse(e, jsonParser)).toList();
default -> value;
};
}
- int interpolate(final String value, final String currentName, final Matcher matcher) {
+ @SneakyThrows
+ int interpolate(final String value, final JsonParser jsonParser, final Matcher matcher) {
+ final String currentName = jsonParser.currentName();
final String varName = matcher.group("varName");
final String placeholder = matcher.group("placeholder");
final String defaultValue = matcher.group("defaultValue");
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedStringDeserializer.java b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedStringDeserializer.java
similarity index 72%
rename from spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedStringDeserializer.java
rename to spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedStringDeserializer.java
index 7a138bd41..780013ba8 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedStringDeserializer.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedStringDeserializer.java
@@ -1,4 +1,4 @@
-package io.github.giulong.spectrum.internals.jackson.deserializers;
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation;
import static lombok.AccessLevel.PRIVATE;
@@ -10,9 +10,7 @@
import io.github.giulong.spectrum.utils.FileUtils;
import lombok.NoArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-@Slf4j
@NoArgsConstructor(access = PRIVATE)
public class InterpolatedStringDeserializer extends InterpolatedDeserializer {
@@ -26,10 +24,6 @@ public static InterpolatedStringDeserializer getInstance() {
@Override
public String deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException {
- final String value = jsonParser.getValueAsString();
- log.trace("Deserializing String from value {}", value);
-
- final String interpolatedValue = interpolate(value, jsonParser.currentName());
- return fileUtils.interpolateTimestampFrom(interpolatedValue);
+ return fileUtils.interpolateTimestampFrom(interpolate(jsonParser));
}
}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/EnvironmentInterpolator.java b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/EnvironmentInterpolator.java
new file mode 100644
index 000000000..a3d3e9db4
--- /dev/null
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/EnvironmentInterpolator.java
@@ -0,0 +1,11 @@
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators;
+
+import java.util.function.Function;
+
+public class EnvironmentInterpolator extends ExternalInterpolator {
+
+ @Override
+ public Function getConsumer() {
+ return System::getenv;
+ }
+}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/ExternalInterpolator.java b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/ExternalInterpolator.java
new file mode 100644
index 000000000..e63fe0116
--- /dev/null
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/ExternalInterpolator.java
@@ -0,0 +1,89 @@
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators;
+
+import static java.util.stream.Collectors.joining;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+
+import com.fasterxml.jackson.annotation.JsonPropertyDescription;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonStreamContext;
+
+import lombok.AllArgsConstructor;
+import lombok.Generated;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Getter
+public abstract class ExternalInterpolator extends Interpolator {
+
+ @SuppressWarnings("unused")
+ @JsonPropertyDescription("Variable prefix")
+ private String prefix;
+
+ @SuppressWarnings("unused")
+ @JsonPropertyDescription("Variable tokens' delimiter")
+ private String delimiter;
+
+ @SuppressWarnings("unused")
+ @JsonPropertyDescription("Function to specify how to transform the original camelCase of the key to match the external variable to search")
+ private TransformCase transformCase;
+
+ public abstract Function getConsumer();
+
+ @Override
+ public Optional findVariableFor(final String value, final JsonParser jsonParser) {
+ final String className = getClass().getSimpleName();
+ final List keyPathTokens = new ArrayList<>();
+ keyPathTokens.add(prefix);
+ getKeyPathTokens(keyPathTokens, jsonParser.getParsingContext());
+
+ final String keyPath = keyPathTokens
+ .stream()
+ .filter(Objects::nonNull)
+ .collect(joining(delimiter));
+
+ final String transformedKeyPath = transformCase.getFunction().apply(keyPath);
+ log.trace("{} is looking for {}", className, transformedKeyPath);
+
+ final String key = getConsumer().apply(transformedKeyPath);
+ if (key != null) {
+ log.debug("{} found {} = {}", className, transformedKeyPath, key);
+ return Optional.of(key);
+ }
+
+ return Optional.empty();
+ }
+
+ void getKeyPathTokens(final List accumulator, final JsonStreamContext context) {
+ final JsonStreamContext parentContext = context.getParent();
+
+ if (parentContext != null) {
+ getKeyPathTokens(accumulator, parentContext);
+ accumulator.add(context.getCurrentName());
+ }
+ }
+
+ @Getter
+ @AllArgsConstructor
+ enum TransformCase {
+
+ NONE("none", s -> s),
+ LOWER("lower", String::toLowerCase),
+ UPPER("upper", String::toUpperCase);
+
+ private final String value;
+ private final Function function;
+
+ @JsonValue
+ @Generated
+ public String getValue() {
+ return value;
+ }
+ }
+}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedDeserializer.java b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/InPlaceInterpolator.java
similarity index 75%
rename from spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedDeserializer.java
rename to spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/InPlaceInterpolator.java
index a5a549074..a9f2743d1 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedDeserializer.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/InPlaceInterpolator.java
@@ -1,22 +1,27 @@
-package io.github.giulong.spectrum.internals.jackson.deserializers;
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators;
+import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.core.JsonParser;
import io.github.giulong.spectrum.utils.Vars;
+import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j
-public abstract class InterpolatedDeserializer extends JsonDeserializer {
+public class InPlaceInterpolator extends Interpolator {
private static final Pattern PATTERN = Pattern.compile("(?\\$\\{(?[\\w.]+)(:-(?[\\w~\\s-.:/\\\\=]*))?})");
private final Vars vars = Vars.getInstance();
- public String interpolate(final String value, final String currentName) {
+ @Override
+ @SneakyThrows
+ public Optional findVariableFor(final String value, final JsonParser jsonParser) {
+ final String currentName = jsonParser.currentName();
final Matcher matcher = PATTERN.matcher(value);
String interpolatedValue = value;
@@ -39,13 +44,10 @@ public String interpolate(final String value, final String currentName) {
}
} else {
log.trace("Interpolated value for key '{}: {}' -> '{}'", currentName, value, interpolatedValue);
-
- if (PATTERN.matcher(interpolatedValue).find()) {
- interpolatedValue = interpolate(interpolatedValue, currentName);
- }
+ interpolatedValue = findVariableFor(interpolatedValue, jsonParser).orElse(interpolatedValue);
}
}
- return interpolatedValue;
+ return interpolatedValue.equals(value) ? Optional.empty() : Optional.of(interpolatedValue);
}
}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/Interpolator.java b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/Interpolator.java
new file mode 100644
index 000000000..5a20ed2fc
--- /dev/null
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/Interpolator.java
@@ -0,0 +1,24 @@
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators;
+
+import java.util.Optional;
+
+import com.fasterxml.jackson.annotation.JacksonInject;
+import com.fasterxml.jackson.annotation.JsonPropertyDescription;
+import com.fasterxml.jackson.core.JsonParser;
+
+import lombok.Getter;
+
+@Getter
+public abstract class Interpolator {
+
+ @SuppressWarnings("unused")
+ @JacksonInject("enabledFromClient")
+ @JsonPropertyDescription("Whether to enable this interpolator. Injected to true by default, so no need to explicitly set it")
+ private boolean enabled;
+
+ @SuppressWarnings("unused")
+ @JsonPropertyDescription("Sets the order of evaluation of this interpolator among others. Higher priority wins.")
+ private int priority;
+
+ public abstract Optional findVariableFor(String value, JsonParser jsonParser);
+}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/PropertiesInterpolator.java b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/PropertiesInterpolator.java
new file mode 100644
index 000000000..89ac41d1d
--- /dev/null
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/PropertiesInterpolator.java
@@ -0,0 +1,11 @@
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators;
+
+import java.util.function.Function;
+
+public class PropertiesInterpolator extends ExternalInterpolator {
+
+ @Override
+ public Function getConsumer() {
+ return System::getProperty;
+ }
+}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/Configuration.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/Configuration.java
index ca4778c09..2d101b271 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/utils/Configuration.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/Configuration.java
@@ -16,6 +16,9 @@
import io.github.giulong.spectrum.drivers.Driver;
import io.github.giulong.spectrum.interfaces.JsonSchemaTypes;
+import io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators.EnvironmentInterpolator;
+import io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators.InPlaceInterpolator;
+import io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators.PropertiesInterpolator;
import io.github.giulong.spectrum.utils.environments.Environment;
import io.github.giulong.spectrum.utils.events.EventsConsumer;
import io.github.giulong.spectrum.utils.testbook.TestBook;
@@ -25,6 +28,7 @@
import lombok.Generated;
import lombok.Getter;
import lombok.NoArgsConstructor;
+import lombok.Setter;
import org.openqa.selenium.chromium.ChromiumDriverLogLevel;
import org.openqa.selenium.firefox.FirefoxDriverLogLevel;
@@ -36,6 +40,11 @@ public class Configuration {
private static final Configuration INSTANCE = new Configuration();
+ @Setter
+ @JsonIgnore
+ @JsonPropertyDescription("Generic configuration. This node is read only from the base configuration.yaml")
+ private Config config;
+
@JsonPropertyDescription("Common vars to interpolate other String values in the configuration")
private Map vars;
@@ -80,6 +89,28 @@ public static Configuration getInstance() {
return INSTANCE;
}
+ @Getter
+ @Generated
+ public static class Config {
+
+ @JsonPropertyDescription("Configuration keys interpolators")
+ private Interpolators interpolators;
+
+ @Getter
+ @Generated
+ public static class Interpolators {
+
+ @JsonPropertyDescription("Environment variables interpolator")
+ private EnvironmentInterpolator environment;
+
+ @JsonPropertyDescription("Properties interpolator")
+ private PropertiesInterpolator properties;
+
+ @JsonPropertyDescription("In-place configuration file interpolator")
+ private InPlaceInterpolator inPlace;
+ }
+ }
+
@Getter
@Generated
public static class Runtime {
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/Reflections.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/Reflections.java
index abc7e9e8b..7932f5b74 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/utils/Reflections.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/Reflections.java
@@ -43,6 +43,19 @@ public static List getFieldsOf(Class> clazz, final Class> limit) {
return fields;
}
+ @SafeVarargs
+ public static List getFieldsValueOf(final Object object, final T... reified) {
+ final Class> clazz = object.getClass();
+ final Class targetClass = getClassOf(reified);
+
+ return getFieldsOf(clazz, clazz.getSuperclass())
+ .stream()
+ .peek(f -> f.setAccessible(true))
+ .map(f -> getValueOf(f, object))
+ .map(targetClass::cast)
+ .toList();
+ }
+
@SneakyThrows
public static Field getField(final String fieldName, final Object object) {
log.trace("Getting field {}.{}", object.getClass().getSimpleName(), fieldName);
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/YamlUtils.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/YamlUtils.java
index 04c04a821..484a30902 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/utils/YamlUtils.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/YamlUtils.java
@@ -5,6 +5,7 @@
import java.time.Duration;
import java.util.Random;
+import java.util.function.BiConsumer;
import ch.qos.logback.classic.Level;
@@ -15,6 +16,7 @@
import io.github.giulong.spectrum.drivers.Driver;
import io.github.giulong.spectrum.internals.jackson.deserializers.*;
+import io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.*;
import io.github.giulong.spectrum.utils.environments.Environment;
import io.github.giulong.spectrum.utils.file_providers.ClientFileProvider;
import io.github.giulong.spectrum.utils.file_providers.FileProvider;
@@ -113,6 +115,14 @@ public void updateWithInternalFile(final T t, final String file) {
updateWithFile(t, file, internalFileProvider);
}
+ public void updateWithInternalNode(final T t, final String node, final String file) {
+ updateNode(t, node, file, internalFileProvider);
+ }
+
+ public void updateWithClientNode(final T t, final String node, final String file) {
+ updateNode(t, node, file, clientFileProvider);
+ }
+
@SneakyThrows
public String write(final Object object) {
return writer.writeValueAsString(object);
@@ -138,6 +148,16 @@ SimpleModule buildDynamicModuleFor(final Class> clazz, final String file) {
return buildModuleFor(clazz, new DynamicDeserializer<>(clazz, file));
}
+ @SneakyThrows
+ T read(final ObjectReader reader, final String file, final String node) {
+ return reader.readValue(yamlMapper.readTree(classLoader.getResourceAsStream(file)).at(node));
+ }
+
+ @SneakyThrows
+ T read(final ObjectReader reader, final String file) {
+ return reader.readValue(classLoader.getResourceAsStream(file));
+ }
+
@SneakyThrows
T read(final ObjectReader reader, final String file, final Class clazz) {
return reader.readValue(classLoader.getResourceAsStream(file), clazz);
@@ -154,32 +174,37 @@ T read(final FileProvider fileProvider, final String file, final Class cl
}
@SneakyThrows
- T readNode(final FileProvider fileProvider, final String node, final String file, final Class clazz) {
+ T readNode(final FileProvider fileProvider, final String name, final String file, final Class clazz) {
final String fileFound = fileProvider.find(file);
if (fileFound == null) {
return null;
}
- log.debug("Reading node '{}' of file '{}' onto an instance of {}", node, fileFound, clazz.getSimpleName());
- final JsonNode root = fileProvider
+ log.debug("Reading node '{}' of file '{}' onto an instance of {}", name, fileFound, clazz.getSimpleName());
+ final JsonNode node = fileProvider
.augment(yamlMapper)
- .readTree(classLoader.getResourceAsStream(fileFound));
+ .readTree(classLoader.getResourceAsStream(fileFound))
+ .at(name);
- return yamlMapper.convertValue(root.at(node), clazz);
+ return yamlMapper.convertValue(node, clazz);
+ }
+
+ void updateNode(final T t, final String name, final String file, final FileProvider fileProvider) {
+ updateAndAccept(t, file, fileProvider, (fileFound, reader) -> read(reader, fileFound, name));
}
- @SneakyThrows
void updateWithFile(final T t, final String file, final FileProvider fileProvider) {
+ updateAndAccept(t, file, fileProvider, (fileFound, reader) -> read(reader, fileFound));
+ }
+
+ void updateAndAccept(final T t, final String file, final FileProvider fileProvider, final BiConsumer callback) {
final String fileFound = fileProvider.find(file);
if (fileFound == null) {
- log.warn("File not found. Skipping update of the instance of {}", t.getClass().getSimpleName());
+ log.debug("File not found. Skipping update of the instance of {}", t.getClass().getSimpleName());
return;
}
log.debug("Updating the instance of {} with file '{}'", t.getClass().getSimpleName(), fileFound);
- fileProvider
- .augment(yamlMapper)
- .withValueToUpdate(t)
- .readValue(classLoader.getResourceAsStream(fileFound));
+ callback.accept(fileFound, fileProvider.augment(yamlMapper).withValueToUpdate(t));
}
}
diff --git a/spectrum/src/main/java/io/github/giulong/spectrum/utils/file_providers/FileProvider.java b/spectrum/src/main/java/io/github/giulong/spectrum/utils/file_providers/FileProvider.java
index 0b5b112be..4364cc861 100644
--- a/spectrum/src/main/java/io/github/giulong/spectrum/utils/file_providers/FileProvider.java
+++ b/spectrum/src/main/java/io/github/giulong/spectrum/utils/file_providers/FileProvider.java
@@ -9,9 +9,9 @@
public interface FileProvider {
default ObjectReader augment(final ObjectMapper mapper) {
return mapper
+ .setInjectableValues(getInjectableValues())
.reader()
- .withView(getViews())
- .with(getInjectableValues());
+ .withView(getViews());
}
Class extends Views> getViews();
diff --git a/spectrum/src/main/resources/yaml/configuration.default.yaml b/spectrum/src/main/resources/yaml/configuration.default.yaml
index 052f1ff4d..ad2516115 100644
--- a/spectrum/src/main/resources/yaml/configuration.default.yaml
+++ b/spectrum/src/main/resources/yaml/configuration.default.yaml
@@ -1,3 +1,20 @@
+# Generic configuration. This node is read only from the base configuration.yaml
+config:
+ interpolators: # Configuration keys interpolators
+ environment: # Environment variables interpolator
+ priority: 0 # Sets the order of evaluation of this interpolator among others. Higher priority wins.
+ prefix: spectrum # Variable prefix
+ delimiter: . # Variable tokens' delimiter
+ transformCase: none # Function to specify how to transform the original camelCase of the key to match the external variable to search
+ properties: # Properties interpolator
+ priority: 1 # Sets the order of evaluation of this interpolator among others. Higher priority wins.
+ prefix: spectrum # Variable prefix
+ delimiter: . # Variable tokens' delimiter
+ transformCase: none # Function to specify how to transform the original camelCase of the key to match the external variable to search
+ inPlace: # In-place configuration file interpolator
+ priority: 2 # Sets the order of evaluation of this interpolator among others. Higher priority wins.
+ enabled: true
+
# Common vars to interpolate other String values in the configuration
vars:
downloadsFolder: ${user.dir}\target\downloads # Path, as you can see below, where to save downloaded files
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumSessionListenerTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumSessionListenerTest.java
index cc9f43789..14d7e85b2 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumSessionListenerTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/SpectrumSessionListenerTest.java
@@ -11,6 +11,7 @@
import io.github.giulong.spectrum.types.ProjectProperties;
import io.github.giulong.spectrum.utils.*;
+import io.github.giulong.spectrum.utils.Configuration.Config;
import io.github.giulong.spectrum.utils.environments.Environment;
import io.github.giulong.spectrum.utils.events.EventsDispatcher;
import io.github.giulong.spectrum.utils.testbook.TestBook;
@@ -67,6 +68,9 @@ class SpectrumSessionListenerTest {
@Mock
private TestBook testBook;
+ @Mock
+ private Config config;
+
@Mock
private Summary summary;
@@ -124,6 +128,9 @@ void launcherSessionOpened() {
when(yamlUtils.readInternal("properties.yaml", ProjectProperties.class)).thenReturn(projectProperties);
when(freeMarkerWrapper.interpolate(banner, projectProperties)).thenReturn(interpolatedBanner);
+ // parseConfig
+ when(yamlUtils.readInternalNode(CONFIG_NODE, DEFAULT_CONFIGURATION_YAML)).thenReturn(config);
+
when(yamlUtils.readClientNode(PROFILE_NODE, CONFIGURATION)).thenReturn(profile);
when(yamlUtils.readInternalNode(PROFILE_NODE, DEFAULT_CONFIGURATION_YAML)).thenReturn("defaultProfile");
when(yamlUtils.readInternalNode(VARS_NODE, DEFAULT_CONFIGURATION_YAML)).thenReturn(Map.of("one", "one"));
@@ -222,7 +229,7 @@ void parseConfigurationUnix() {
System.setProperty("os.name", "nix");
- // parseProfile
+ // parseProfiles
when(yamlUtils.readClientNode(PROFILE_NODE, CONFIGURATION)).thenReturn(profile);
when(yamlUtils.readInternalNode(PROFILE_NODE, DEFAULT_CONFIGURATION_YAML)).thenReturn("defaultProfile");
@@ -258,6 +265,32 @@ static Stream profilesValuesProvider() {
arguments(null, " ,,default-profile,another", List.of("default-profile", "another")));
}
+ @Test
+ @DisplayName("parseConfig should parse the config node read from the configuration yaml files")
+ void parseConfig() {
+ System.setProperty("os.name", "Win");
+
+ when(yamlUtils.readInternalNode(CONFIG_NODE, DEFAULT_CONFIGURATION_YAML)).thenReturn(config);
+
+ spectrumSessionListener.parseConfig();
+
+ verify(yamlUtils, never()).updateWithInternalNode(config, CONFIG_NODE, DEFAULT_CONFIGURATION_UNIX_YAML);
+ verify(yamlUtils).updateWithClientNode(config, CONFIG_NODE, CONFIGURATION);
+ }
+
+ @Test
+ @DisplayName("parseConfig should parse the also the config node read from the internal configuration.default.unix.yaml")
+ void parseConfigUnix() {
+ System.setProperty("os.name", "nix");
+
+ when(yamlUtils.readInternalNode(CONFIG_NODE, DEFAULT_CONFIGURATION_YAML)).thenReturn(config);
+
+ spectrumSessionListener.parseConfig();
+
+ verify(yamlUtils).updateWithInternalNode(config, CONFIG_NODE, DEFAULT_CONFIGURATION_UNIX_YAML);
+ verify(yamlUtils).updateWithClientNode(config, CONFIG_NODE, CONFIGURATION);
+ }
+
@DisplayName("parseVars should put in the VARS map all the variables read from the configuration yaml files")
@ParameterizedTest
@MethodSource("varsValuesProvider")
@@ -272,6 +305,8 @@ void parseVars(final Map defaultVars, final Map
spectrumSessionListener.parseVars(List.of(profileConfiguration));
assertEquals(expected, Vars.getInstance());
+
+ verify(yamlUtils, never()).updateWithInternalNode(config, VARS_NODE, DEFAULT_CONFIGURATION_UNIX_YAML);
}
@DisplayName("parseVars should put in the VARS map also those read from the internal configuration.default.unix.yaml")
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedBooleanDeserializerTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedBooleanDeserializerTest.java
deleted file mode 100644
index 9ebe77493..000000000
--- a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedBooleanDeserializerTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package io.github.giulong.spectrum.internals.jackson.deserializers;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.params.provider.Arguments.arguments;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-import java.util.stream.Stream;
-
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.databind.DeserializationContext;
-
-import io.github.giulong.spectrum.utils.Vars;
-
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-
-class InterpolatedBooleanDeserializerTest {
-
- @Mock
- private JsonParser jsonParser;
-
- @Mock
- private DeserializationContext deserializationContext;
-
- @InjectMocks
- private InterpolatedBooleanDeserializer interpolatedBooleanDeserializer;
-
- private static final String VAR_IN_ENV = "varInEnv";
-
- @BeforeAll
- public static void beforeAll() {
- Vars.getInstance().put("varInEnv", VAR_IN_ENV);
- }
-
- @AfterAll
- public static void afterAll() {
- Vars.getInstance().clear();
- }
-
- @Test
- @DisplayName("getInstance should return the singleton")
- void getInstance() {
- //noinspection EqualsWithItself
- assertSame(InterpolatedBooleanDeserializer.getInstance(), InterpolatedBooleanDeserializer.getInstance());
- }
-
- @DisplayName("deserialize should delegate to the parent method passing the string value")
- @ParameterizedTest(name = "with value {0} we expect {1}")
- @MethodSource("valuesProvider")
- void deserialize(final String value, final boolean expected) throws IOException {
- when(jsonParser.getValueAsString()).thenReturn(value);
- when(jsonParser.currentName()).thenReturn("key");
-
- assertEquals(expected, interpolatedBooleanDeserializer.deserialize(jsonParser, deserializationContext));
- }
-
- static Stream valuesProvider() {
- return Stream.of(
- arguments("true", true),
- arguments("false", false),
- arguments("${not.set:-true}", true),
- arguments("${notSet:-true}", true),
- arguments("${notSet:-}", false),
- arguments("${varInEnv:-true}", false),
- arguments("${varInEnv}", false),
- arguments("${not.set}", false),
- arguments("${notSet}", false));
- }
-}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/DriverDeserializerTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/DriverDeserializerTest.java
similarity index 57%
rename from spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/DriverDeserializerTest.java
rename to spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/DriverDeserializerTest.java
index b132ad934..4da6361d3 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/DriverDeserializerTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/DriverDeserializerTest.java
@@ -1,18 +1,28 @@
-package io.github.giulong.spectrum.internals.jackson.deserializers;
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
import java.util.stream.Stream;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import io.github.giulong.spectrum.drivers.*;
+import io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators.Interpolator;
+import io.github.giulong.spectrum.utils.Configuration;
+import io.github.giulong.spectrum.utils.Configuration.Config;
+import io.github.giulong.spectrum.utils.Configuration.Config.Interpolators;
+import io.github.giulong.spectrum.utils.Reflections;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -20,9 +30,24 @@
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
+import org.mockito.MockedStatic;
class DriverDeserializerTest {
+ private MockedStatic reflectionsMockedStatic;
+
+ @Mock
+ private Configuration configuration;
+
+ @Mock
+ private Config config;
+
+ @Mock
+ private Interpolators interpolators;
+
+ @Mock
+ private Interpolator interpolator;
+
@Mock
private JsonParser jsonParser;
@@ -30,7 +55,19 @@ class DriverDeserializerTest {
private DeserializationContext deserializationContext;
@InjectMocks
- private DriverDeserializer driverDeserializer;
+ private DriverDeserializer deserializer;
+
+ @BeforeEach
+ void beforeEach() {
+ Reflections.setField("configuration", deserializer, configuration);
+
+ reflectionsMockedStatic = mockStatic(Reflections.class);
+ }
+
+ @AfterEach
+ void afterEach() {
+ reflectionsMockedStatic.close();
+ }
@Test
@DisplayName("getInstance should return the singleton")
@@ -43,26 +80,27 @@ void getInstance() {
@ParameterizedTest(name = "with value {0} we expect {1}")
@MethodSource("valuesProvider")
void deserialize(final String value, final Driver, ?, ?> expected) throws IOException {
+ interpolateStubsFor(value);
+
when(jsonParser.getValueAsString()).thenReturn(value);
- when(jsonParser.currentName()).thenReturn("key");
- assertInstanceOf(expected.getClass(), driverDeserializer.deserialize(jsonParser, deserializationContext));
+ assertInstanceOf(expected.getClass(), deserializer.deserialize(jsonParser, deserializationContext));
}
@Test
@DisplayName("deserialize should throw an exception if the provided key is not a valid driver name")
void deserializeNotExisting() throws IOException {
- String notValidDriver = "notValidDriver";
+ final String notValidDriver = "notValidDriver";
+
+ interpolateStubsFor(notValidDriver);
when(jsonParser.getValueAsString()).thenReturn(notValidDriver);
- when(jsonParser.currentName()).thenReturn("key");
- Exception exception = assertThrows(IllegalArgumentException.class, () -> driverDeserializer.deserialize(jsonParser, deserializationContext));
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> deserializer.deserialize(jsonParser, deserializationContext));
assertEquals("Value '" + notValidDriver + "' is not a valid driver!", exception.getMessage());
}
static Stream valuesProvider() {
return Stream.of(
- arguments("${justToTestInterpolation:-chrome}", mock(Chrome.class)),
arguments("chrome", mock(Chrome.class)),
arguments("firefox", mock(Firefox.class)),
arguments("edge", mock(Edge.class)),
@@ -74,4 +112,13 @@ static Stream valuesProvider() {
arguments("mac2", mock(Mac2.class)),
arguments("appiumGeneric", mock(AppiumGeneric.class)));
}
+
+ private void interpolateStubsFor(final String value) {
+ when(configuration.getConfig()).thenReturn(config);
+ when(config.getInterpolators()).thenReturn(interpolators);
+ when(Reflections.getFieldsValueOf(interpolators)).thenReturn(List.of(interpolator));
+ when(interpolator.isEnabled()).thenReturn(true);
+
+ when(interpolator.findVariableFor(value, jsonParser)).thenReturn(Optional.of(value));
+ }
}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/EnvironmentDeserializerTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/EnvironmentDeserializerTest.java
similarity index 50%
rename from spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/EnvironmentDeserializerTest.java
rename to spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/EnvironmentDeserializerTest.java
index e1cfd0db5..1a874947e 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/EnvironmentDeserializerTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/EnvironmentDeserializerTest.java
@@ -1,21 +1,35 @@
-package io.github.giulong.spectrum.internals.jackson.deserializers;
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
import java.util.stream.Stream;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
+import io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators.Interpolator;
+import io.github.giulong.spectrum.utils.Configuration;
+import io.github.giulong.spectrum.utils.Configuration.Config;
+import io.github.giulong.spectrum.utils.Configuration.Config.Interpolators;
+import io.github.giulong.spectrum.utils.Reflections;
import io.github.giulong.spectrum.utils.environments.AppiumEnvironment;
import io.github.giulong.spectrum.utils.environments.Environment;
import io.github.giulong.spectrum.utils.environments.GridEnvironment;
import io.github.giulong.spectrum.utils.environments.LocalEnvironment;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -23,9 +37,24 @@
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
+import org.mockito.MockedStatic;
class EnvironmentDeserializerTest {
+ private MockedStatic reflectionsMockedStatic;
+
+ @Mock
+ private Configuration configuration;
+
+ @Mock
+ private Config config;
+
+ @Mock
+ private Interpolators interpolators;
+
+ @Mock
+ private Interpolator interpolator;
+
@Mock
private JsonParser jsonParser;
@@ -33,7 +62,19 @@ class EnvironmentDeserializerTest {
private DeserializationContext deserializationContext;
@InjectMocks
- private EnvironmentDeserializer environmentDeserializer;
+ private EnvironmentDeserializer deserializer;
+
+ @BeforeEach
+ void beforeEach() {
+ Reflections.setField("configuration", deserializer, configuration);
+
+ reflectionsMockedStatic = mockStatic(Reflections.class);
+ }
+
+ @AfterEach
+ void afterEach() {
+ reflectionsMockedStatic.close();
+ }
@Test
@DisplayName("getInstance should return the singleton")
@@ -46,28 +87,41 @@ void getInstance() {
@ParameterizedTest(name = "with value {0} we expect {1}")
@MethodSource("valuesProvider")
void deserialize(final String value, final Environment expected) throws IOException {
+ interpolateStubsFor(value);
+
when(jsonParser.getValueAsString()).thenReturn(value);
- when(jsonParser.currentName()).thenReturn("key");
- assertInstanceOf(expected.getClass(), environmentDeserializer.deserialize(jsonParser, deserializationContext));
+ assertInstanceOf(expected.getClass(), deserializer.deserialize(jsonParser, deserializationContext));
+ }
+
+ static Stream valuesProvider() {
+ return Stream.of(
+ arguments("local", mock(LocalEnvironment.class)),
+ arguments("grid", mock(GridEnvironment.class)),
+ arguments("appium", mock(AppiumEnvironment.class)));
}
@Test
@DisplayName("deserialize should throw an exception if the provided key is not a valid environment name")
void deserializeNotExisting() throws IOException {
- String notValidEnvironment = "notValidEnvironment";
+ final String notValidEnvironment = "notValidEnvironment";
+
+ interpolateStubsFor(notValidEnvironment);
+
when(jsonParser.getValueAsString()).thenReturn(notValidEnvironment);
- when(jsonParser.currentName()).thenReturn("key");
- Exception exception = assertThrows(IllegalArgumentException.class, () -> environmentDeserializer.deserialize(jsonParser, deserializationContext));
+ final Exception exception = assertThrows(IllegalArgumentException.class, () -> deserializer.deserialize(jsonParser, deserializationContext));
assertEquals("Value '" + notValidEnvironment + "' is not a valid environment!", exception.getMessage());
+
+ verifyNoInteractions(deserializationContext);
}
- static Stream valuesProvider() {
- return Stream.of(
- arguments("${justToTestInterpolation:-local}", mock(LocalEnvironment.class)),
- arguments("local", mock(LocalEnvironment.class)),
- arguments("grid", mock(GridEnvironment.class)),
- arguments("appium", mock(AppiumEnvironment.class)));
+ private void interpolateStubsFor(final String value) {
+ when(configuration.getConfig()).thenReturn(config);
+ when(config.getInterpolators()).thenReturn(interpolators);
+ when(Reflections.getFieldsValueOf(interpolators)).thenReturn(List.of(interpolator));
+ when(interpolator.isEnabled()).thenReturn(true);
+
+ when(interpolator.findVariableFor(value, jsonParser)).thenReturn(Optional.of(value));
}
}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedBooleanDeserializerTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedBooleanDeserializerTest.java
new file mode 100644
index 000000000..10da8fdab
--- /dev/null
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedBooleanDeserializerTest.java
@@ -0,0 +1,103 @@
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+
+import io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators.Interpolator;
+import io.github.giulong.spectrum.utils.Configuration;
+import io.github.giulong.spectrum.utils.Configuration.Config;
+import io.github.giulong.spectrum.utils.Configuration.Config.Interpolators;
+import io.github.giulong.spectrum.utils.Reflections;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+
+class InterpolatedBooleanDeserializerTest {
+
+ private MockedStatic reflectionsMockedStatic;
+
+ @Mock
+ private Configuration configuration;
+
+ @Mock
+ private Config config;
+
+ @Mock
+ private Interpolators interpolators;
+
+ @Mock
+ private Interpolator interpolator;
+
+ @Mock
+ private JsonParser jsonParser;
+
+ @Mock
+ private DeserializationContext deserializationContext;
+
+ @InjectMocks
+ private InterpolatedBooleanDeserializer deserializer;
+
+ @BeforeEach
+ void beforeEach() {
+ Reflections.setField("configuration", deserializer, configuration);
+
+ reflectionsMockedStatic = mockStatic(Reflections.class);
+ }
+
+ @AfterEach
+ void afterEach() {
+ reflectionsMockedStatic.close();
+ }
+
+ @Test
+ @DisplayName("getInstance should return the singleton")
+ void getInstance() {
+ //noinspection EqualsWithItself
+ assertSame(InterpolatedBooleanDeserializer.getInstance(), InterpolatedBooleanDeserializer.getInstance());
+ }
+
+ @DisplayName("deserialize should delegate to the parent method passing the string value")
+ @ParameterizedTest(name = "with value {0} we expect {1}")
+ @MethodSource("valuesProvider")
+ void deserialize(final String value, final boolean expected) throws IOException {
+ interpolateStubsFor(value);
+
+ when(jsonParser.getValueAsString()).thenReturn(value);
+
+ assertEquals(expected, deserializer.deserialize(jsonParser, deserializationContext));
+ }
+
+ static Stream valuesProvider() {
+ return Stream.of(
+ arguments("true", true),
+ arguments("false", false));
+ }
+
+ private void interpolateStubsFor(final String value) {
+ when(configuration.getConfig()).thenReturn(config);
+ when(config.getInterpolators()).thenReturn(interpolators);
+ when(Reflections.getFieldsValueOf(interpolators)).thenReturn(List.of(interpolator));
+ when(interpolator.isEnabled()).thenReturn(true);
+
+ when(interpolator.findVariableFor(value, jsonParser)).thenReturn(Optional.of(value));
+ }
+}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedDeserializerTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedDeserializerTest.java
new file mode 100644
index 000000000..6e20882cc
--- /dev/null
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedDeserializerTest.java
@@ -0,0 +1,130 @@
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.Optional;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+
+import io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators.Interpolator;
+import io.github.giulong.spectrum.utils.Configuration;
+import io.github.giulong.spectrum.utils.Configuration.Config;
+import io.github.giulong.spectrum.utils.Configuration.Config.Interpolators;
+import io.github.giulong.spectrum.utils.Reflections;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+
+class InterpolatedDeserializerTest {
+
+ private final String value = "value";
+
+ private MockedStatic reflectionsMockedStatic;
+
+ @Mock
+ private Configuration configuration;
+
+ @Mock
+ private Config config;
+
+ @Mock
+ private Interpolators interpolators;
+
+ @Mock
+ private Interpolator interpolator1;
+
+ @Mock
+ private Interpolator interpolator2;
+
+ @Mock
+ private Interpolator interpolator3;
+
+ @Mock
+ private Interpolator interpolator4;
+
+ @Mock
+ private JsonParser jsonParser;
+
+ @InjectMocks
+ private DummyInterpolatedDeserializer deserializer;
+
+ @BeforeEach
+ void beforeEach() {
+ Reflections.setField("configuration", deserializer, configuration);
+
+ reflectionsMockedStatic = mockStatic(Reflections.class);
+ }
+
+ @AfterEach
+ void afterEach() {
+ reflectionsMockedStatic.close();
+ }
+
+ @Test
+ @DisplayName("interpolate should just return the provided value when config is null")
+ void interpolateNoConfig() {
+ when(configuration.getConfig()).thenReturn(null);
+
+ assertEquals(value, deserializer.interpolate(value, jsonParser));
+ }
+
+ @Test
+ @DisplayName("interpolate should return the last interpolated value of the interpolators which found the key, respecting their priorities")
+ void interpolate() {
+ final String interpolatedValue1 = "interpolatedValue1";
+
+ when(configuration.getConfig()).thenReturn(config);
+ when(config.getInterpolators()).thenReturn(interpolators);
+ when(Reflections.getFieldsValueOf(interpolators)).thenReturn(List.of(interpolator1, interpolator2, interpolator3, interpolator4));
+ when(interpolator1.isEnabled()).thenReturn(false);
+ when(interpolator2.isEnabled()).thenReturn(true);
+ when(interpolator3.isEnabled()).thenReturn(true);
+ when(interpolator4.isEnabled()).thenReturn(true);
+
+ when(interpolator2.getPriority()).thenReturn(0);
+ when(interpolator3.getPriority()).thenReturn(2);
+ when(interpolator4.getPriority()).thenReturn(1);
+
+ when(interpolator3.findVariableFor(value, jsonParser)).thenReturn(Optional.of(interpolatedValue1));
+
+ assertEquals(interpolatedValue1, deserializer.interpolate(value, jsonParser));
+
+ verifyNoMoreInteractions(interpolator1);
+ verifyNoMoreInteractions(interpolator2);
+ verifyNoMoreInteractions(interpolator4);
+ }
+
+ @Test
+ @DisplayName("interpolate should return the provided value when no interpolators found the key")
+ void interpolateEmpty() {
+ when(configuration.getConfig()).thenReturn(config);
+ when(config.getInterpolators()).thenReturn(interpolators);
+ when(Reflections.getFieldsValueOf(interpolators)).thenReturn(List.of(interpolator1, interpolator2));
+ when(interpolator1.isEnabled()).thenReturn(false);
+ when(interpolator2.isEnabled()).thenReturn(true);
+
+ when(interpolator2.findVariableFor(value, jsonParser)).thenReturn(Optional.empty());
+
+ assertEquals(value, deserializer.interpolate(value, jsonParser));
+
+ verifyNoMoreInteractions(interpolator1);
+ }
+
+ private static final class DummyInterpolatedDeserializer extends InterpolatedDeserializer {
+
+ @Override
+ public String deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) {
+ return "";
+ }
+ }
+}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedObjectDeserializerTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedObjectDeserializerTest.java
similarity index 96%
rename from spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedObjectDeserializerTest.java
rename to spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedObjectDeserializerTest.java
index b052189dc..518038a4f 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedObjectDeserializerTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedObjectDeserializerTest.java
@@ -1,4 +1,4 @@
-package io.github.giulong.spectrum.internals.jackson.deserializers;
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation;
import static com.fasterxml.jackson.databind.node.JsonNodeType.*;
import static com.github.stefanbirkner.systemlambda.SystemLambda.withEnvironmentVariable;
@@ -98,7 +98,7 @@ void deserializeStrings() throws IOException {
when(jsonNode.getNodeType()).thenReturn(STRING);
when(jsonNode.textValue()).thenReturn(value);
when(InterpolatedStringDeserializer.getInstance()).thenReturn(interpolatedStringDeserializer);
- when(interpolatedStringDeserializer.interpolate(value, currentName)).thenReturn(expected);
+ when(interpolatedStringDeserializer.interpolate(value, jsonParser)).thenReturn(expected);
assertEquals(expected, interpolatedObjectDeserializer.deserialize(jsonParser, deserializationContext));
}
@@ -149,7 +149,7 @@ void deserializeMap() throws IOException {
when(objectMapper.convertValue(jsonNode, Map.class)).thenReturn(map);
when(InterpolatedStringDeserializer.getInstance()).thenReturn(interpolatedStringDeserializer);
- when(interpolatedStringDeserializer.interpolate(value, currentName)).thenReturn(interpolatedValue);
+ when(interpolatedStringDeserializer.interpolate(value, jsonParser)).thenReturn(interpolatedValue);
assertEquals(interpolatedMap, interpolatedObjectDeserializer.deserialize(jsonParser, deserializationContext));
}
@@ -178,7 +178,7 @@ void deserializeMapWithNestedMapsAndLists() throws IOException {
when(objectMapper.convertValue(jsonNode, Map.class)).thenReturn(map);
when(InterpolatedStringDeserializer.getInstance()).thenReturn(interpolatedStringDeserializer);
- when(interpolatedStringDeserializer.interpolate(value, currentName)).thenReturn(interpolatedValue);
+ when(interpolatedStringDeserializer.interpolate(value, jsonParser)).thenReturn(interpolatedValue);
assertEquals(interpolatedMap, interpolatedObjectDeserializer.deserialize(jsonParser, deserializationContext));
}
@@ -199,7 +199,7 @@ void deserializeList() throws IOException {
when(objectMapper.convertValue(jsonNode, List.class)).thenReturn(list);
when(InterpolatedStringDeserializer.getInstance()).thenReturn(interpolatedStringDeserializer);
- when(interpolatedStringDeserializer.interpolate(value, currentName)).thenReturn(interpolatedValue);
+ when(interpolatedStringDeserializer.interpolate(value, jsonParser)).thenReturn(interpolatedValue);
assertEquals(interpolatedList, interpolatedObjectDeserializer.deserialize(jsonParser, deserializationContext));
}
@@ -226,7 +226,7 @@ void deserializeListWithNestedMapsAndLists() throws IOException {
when(objectMapper.convertValue(jsonNode, List.class)).thenReturn(list);
when(InterpolatedStringDeserializer.getInstance()).thenReturn(interpolatedStringDeserializer);
- when(interpolatedStringDeserializer.interpolate(value, currentName)).thenReturn(interpolatedValue);
+ when(interpolatedStringDeserializer.interpolate(value, jsonParser)).thenReturn(interpolatedValue);
assertEquals(interpolatedList, interpolatedObjectDeserializer.deserialize(jsonParser, deserializationContext));
}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedStringDeserializerTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedStringDeserializerTest.java
new file mode 100644
index 000000000..8d78d9c97
--- /dev/null
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/InterpolatedStringDeserializerTest.java
@@ -0,0 +1,92 @@
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.matchesPattern;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+
+import io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators.Interpolator;
+import io.github.giulong.spectrum.utils.Configuration;
+import io.github.giulong.spectrum.utils.Configuration.Config;
+import io.github.giulong.spectrum.utils.Configuration.Config.Interpolators;
+import io.github.giulong.spectrum.utils.Reflections;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+
+class InterpolatedStringDeserializerTest {
+
+ private MockedStatic reflectionsMockedStatic;
+
+ @Mock
+ private Configuration configuration;
+
+ @Mock
+ private Config config;
+
+ @Mock
+ private Interpolators interpolators;
+
+ @Mock
+ private Interpolator interpolator;
+
+ @Mock
+ private JsonParser jsonParser;
+
+ @Mock
+ private DeserializationContext deserializationContext;
+
+ @InjectMocks
+ private InterpolatedStringDeserializer deserializer;
+
+ @BeforeEach
+ void beforeEach() {
+ Reflections.setField("configuration", deserializer, configuration);
+
+ reflectionsMockedStatic = mockStatic(Reflections.class);
+ }
+
+ @AfterEach
+ void afterEach() {
+ reflectionsMockedStatic.close();
+ }
+
+ @Test
+ @DisplayName("getInstance should return the singleton")
+ void getInstance() {
+ //noinspection EqualsWithItself
+ assertSame(InterpolatedStringDeserializer.getInstance(), InterpolatedStringDeserializer.getInstance());
+ }
+
+ @Test
+ @DisplayName("deserialize should delegate to the parent method passing the string value, interpolating the timestamp")
+ void deserializeTimestamp() throws IOException {
+ final String value = "value-${timestamp}";
+
+ when(jsonParser.getValueAsString()).thenReturn(value);
+
+ // parent interpolate
+ when(configuration.getConfig()).thenReturn(config);
+ when(config.getInterpolators()).thenReturn(interpolators);
+ when(Reflections.getFieldsValueOf(interpolators)).thenReturn(List.of(interpolator));
+ when(interpolator.isEnabled()).thenReturn(true);
+ when(interpolator.findVariableFor(value, jsonParser)).thenReturn(Optional.of(value));
+
+ assertThat(deserializer.deserialize(jsonParser, deserializationContext), matchesPattern("value-[0-9]{2}-[0-9]{2}-[0-9]{4}_[0-9]{2}-[0-9]{2}-[0-9]{2}"));
+ verifyNoInteractions(deserializationContext);
+ }
+}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/EnvironmentInterpolatorTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/EnvironmentInterpolatorTest.java
new file mode 100644
index 000000000..fe842e9eb
--- /dev/null
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/EnvironmentInterpolatorTest.java
@@ -0,0 +1,31 @@
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators;
+
+import static com.github.stefanbirkner.systemlambda.SystemLambda.withEnvironmentVariable;
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+
+class EnvironmentInterpolatorTest {
+
+ private final String property = "property";
+
+ @InjectMocks
+ private EnvironmentInterpolator interpolator;
+
+ @AfterEach
+ void afterEach() {
+ System.clearProperty(property);
+ }
+
+ @Test
+ @DisplayName("getConsumer should return System::getenv")
+ void getConsumer() throws Exception {
+ final String value = "value";
+
+ withEnvironmentVariable(property, value)
+ .execute(() -> assertEquals(value, interpolator.getConsumer().apply(property)));
+ }
+}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/ExternalInterpolatorTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/ExternalInterpolatorTest.java
new file mode 100644
index 000000000..6f64cca10
--- /dev/null
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/ExternalInterpolatorTest.java
@@ -0,0 +1,151 @@
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators;
+
+import static io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators.ExternalInterpolator.TransformCase.LOWER;
+import static io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators.ExternalInterpolator.TransformCase.NONE;
+import static io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators.ExternalInterpolator.TransformCase.UPPER;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonStreamContext;
+
+import io.github.giulong.spectrum.utils.Reflections;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.mockito.InOrder;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+class ExternalInterpolatorTest {
+
+ private final String value = "value";
+
+ @Mock
+ private JsonParser jsonParser;
+
+ @Mock
+ private List accumulator;
+
+ @Mock
+ private JsonStreamContext context;
+
+ @Mock
+ private JsonStreamContext parentContext;
+
+ @Mock
+ private JsonStreamContext grandParentContext;
+
+ @InjectMocks
+ private DummyExternalInterpolator interpolator;
+
+ @InjectMocks
+ private DummyNotFoundExternalInterpolator interpolatorNotFound;
+
+ @BeforeEach
+ void beforeEach() {
+ Reflections.setField("prefix", interpolator, "prefix");
+ Reflections.setField("delimiter", interpolator, ".");
+ Reflections.setField("transformCase", interpolator, UPPER);
+
+ Reflections.setField("prefix", interpolatorNotFound, "prefix");
+ Reflections.setField("delimiter", interpolatorNotFound, ".");
+ Reflections.setField("transformCase", interpolatorNotFound, UPPER);
+ }
+
+ @Test
+ @DisplayName("findVariableFor should return an empty optional if no variable is found")
+ void findVariableForEmpty() {
+ when(context.getParent()).thenReturn(null);
+ when(jsonParser.getParsingContext()).thenReturn(context);
+
+ assertEquals(Optional.empty(), interpolatorNotFound.findVariableFor(value, jsonParser));
+ }
+
+ @Test
+ @DisplayName("findVariableFor should return an optional containing the variable found")
+ void findVariableFor() {
+ when(context.getParent()).thenReturn(null);
+ when(jsonParser.getParsingContext()).thenReturn(context);
+
+ assertEquals(Optional.of("PREFIXFound"), interpolator.findVariableFor(value, jsonParser));
+ }
+
+ @Test
+ @DisplayName("getKeyPathTokens should stop when parent context is null")
+ void getKeyPathTokensNoParentContext() {
+ when(context.getParent()).thenReturn(null);
+
+ interpolator.getKeyPathTokens(accumulator, context);
+
+ verifyNoInteractions(accumulator);
+ verifyNoMoreInteractions(context);
+ }
+
+ @Test
+ @DisplayName("getKeyPathTokens should collect all the tokens in the provided accumulator")
+ void getKeyPathTokens() {
+ final String currentName = "currentName";
+ final String parentCurrentName = "parentCurrentName";
+
+ when(context.getParent()).thenReturn(parentContext);
+ when(parentContext.getParent()).thenReturn(grandParentContext);
+ when(grandParentContext.getParent()).thenReturn(null);
+
+ when(context.getCurrentName()).thenReturn(currentName);
+ when(parentContext.getCurrentName()).thenReturn(parentCurrentName);
+
+ final InOrder inOrder = inOrder(accumulator);
+
+ interpolator.getKeyPathTokens(accumulator, context);
+
+ inOrder.verify(accumulator).add(parentCurrentName);
+ inOrder.verify(accumulator).add(currentName);
+ verifyNoMoreInteractions(grandParentContext);
+ }
+
+ @Test
+ @DisplayName("TransformCase none should return the original string provided")
+ void transformCaseNone() {
+ final String string = "string";
+ assertEquals(string, NONE.getFunction().apply(string));
+ }
+
+ @Test
+ @DisplayName("TransformCase lower should return the provided string lowercased")
+ void transformCaseLower() {
+ final String string = "string";
+ assertEquals(string.toLowerCase(), LOWER.getFunction().apply(string));
+ }
+
+ @Test
+ @DisplayName("TransformCase upper should return the provided string uppercased")
+ void transformCaseUpper() {
+ final String string = "string";
+ assertEquals(string.toUpperCase(), UPPER.getFunction().apply(string));
+ }
+
+ private static final class DummyExternalInterpolator extends ExternalInterpolator {
+
+ @Override
+ public Function getConsumer() {
+ return s -> s + "Found";
+ }
+ }
+
+ private static final class DummyNotFoundExternalInterpolator extends ExternalInterpolator {
+
+ @Override
+ public Function getConsumer() {
+ return s -> null;
+ }
+ }
+}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedStringDeserializerTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/InPlaceInterpolatorTest.java
similarity index 53%
rename from spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedStringDeserializerTest.java
rename to spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/InPlaceInterpolatorTest.java
index 91fb83149..2954d2c06 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/InterpolatedStringDeserializerTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/InPlaceInterpolatorTest.java
@@ -1,39 +1,40 @@
-package io.github.giulong.spectrum.internals.jackson.deserializers;
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators;
import static com.github.stefanbirkner.systemlambda.SystemLambda.withEnvironmentVariable;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.matchesPattern;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.mockito.Mockito.when;
import java.io.IOException;
+import java.util.Optional;
import java.util.stream.Stream;
import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.databind.DeserializationContext;
import io.github.giulong.spectrum.utils.Vars;
-import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
-class InterpolatedStringDeserializerTest {
+class InPlaceInterpolatorTest {
private static final String VAR_IN_ENV = "varInEnv";
- @Mock
- private JsonParser jsonParser;
+ private final String currentName = "currentName";
@Mock
- private DeserializationContext deserializationContext;
+ private JsonParser jsonParser;
@InjectMocks
- private InterpolatedStringDeserializer interpolatedStringDeserializer;
+ private InPlaceInterpolator interpolator;
@BeforeAll
public static void beforeAll() {
@@ -45,26 +46,17 @@ public static void afterAll() {
Vars.getInstance().clear();
}
- @Test
- @DisplayName("getInstance should return the singleton")
- void getInstance() {
- //noinspection EqualsWithItself
- assertSame(InterpolatedStringDeserializer.getInstance(), InterpolatedStringDeserializer.getInstance());
- }
-
- @DisplayName("deserialize should delegate to the parent method passing the string value")
+ @DisplayName("findVariableFor should find the right value and return an optional containing it")
@ParameterizedTest(name = "with value {0} we expect {1}")
@MethodSource("valuesProvider")
void deserialize(final String value, final String expected) throws IOException {
- when(jsonParser.getValueAsString()).thenReturn(value);
- when(jsonParser.currentName()).thenReturn("not important");
+ when(jsonParser.currentName()).thenReturn(currentName);
- assertEquals(expected, interpolatedStringDeserializer.deserialize(jsonParser, deserializationContext));
+ assertEquals(Optional.of(expected), interpolator.findVariableFor(value, jsonParser));
}
static Stream valuesProvider() {
return Stream.of(
- arguments("value", "value"),
arguments("${not.set:-_-}", "_-"),
arguments("${not.set:-- -}", "- -"),
arguments("${not.set:--\t-}", "-\t-"),
@@ -79,46 +71,40 @@ static Stream valuesProvider() {
arguments("${notSet:-}", ""),
arguments("${varInEnv:-local}", VAR_IN_ENV),
arguments("${varInEnv}", VAR_IN_ENV),
- arguments("${varInEnv:-~/local}", VAR_IN_ENV),
- arguments("${not.set}", "${not.set}"),
- arguments("${notSet}", "${notSet}"));
+ arguments("${varInEnv:-~/local}", VAR_IN_ENV));
}
- @Test
- @DisplayName("deserialize should interpolate the timestamp")
- void deserializeTimestamp() throws IOException {
- final String value = "value-${timestamp}";
-
- when(jsonParser.getValueAsString()).thenReturn(value);
- when(jsonParser.currentName()).thenReturn("not important");
+ @DisplayName("findVariableFor should return an empty optional if no value to interpolate is found")
+ @ParameterizedTest(name = "with value {0}")
+ @ValueSource(strings = {"value", "${not.set}", "${notSet}"})
+ void deserializeNotMatching(final String value) throws IOException {
+ when(jsonParser.currentName()).thenReturn(currentName);
- assertThat(interpolatedStringDeserializer.deserialize(jsonParser, deserializationContext), matchesPattern("value-[0-9]{2}-[0-9]{2}-[0-9]{4}_[0-9]{2}-[0-9]{2}-[0-9]{2}"));
+ assertEquals(Optional.empty(), interpolator.findVariableFor(value, jsonParser));
}
@Test
- @DisplayName("deserialize should consider system properties")
+ @DisplayName("findVariableFor should consider system properties")
void deserializeFromSystemProperty() throws Exception {
final String expected = "expected";
- System.setProperty("systemProperty", expected);
- when(jsonParser.getValueAsString()).thenReturn("${systemProperty:-local}");
- when(jsonParser.currentName()).thenReturn("not important");
+ System.setProperty("systemProperty", expected);
+ when(jsonParser.currentName()).thenReturn(currentName);
// We set the "systemProperty" env var with a random value just to check the precedence: system property wins
withEnvironmentVariable("systemProperty", "SOME VALUE")
- .execute(() -> assertEquals(expected, interpolatedStringDeserializer.deserialize(jsonParser, deserializationContext)));
+ .execute(() -> assertEquals(Optional.of(expected), interpolator.findVariableFor("${systemProperty:-local}", jsonParser)));
System.clearProperty("systemProperty");
}
@Test
- @DisplayName("deserialize should consider env variables")
+ @DisplayName("findVariableFor should consider env variables")
void deserializeFromEnvVariables() throws Exception {
final String expected = "expected";
- when(jsonParser.getValueAsString()).thenReturn("${envVar:-local}");
- when(jsonParser.currentName()).thenReturn("not important");
+ when(jsonParser.currentName()).thenReturn(currentName);
- withEnvironmentVariable("envVar", expected).execute(() -> assertEquals(expected, interpolatedStringDeserializer.deserialize(jsonParser, deserializationContext)));
+ withEnvironmentVariable("envVar", expected).execute(() -> assertEquals(Optional.of(expected), interpolator.findVariableFor("${envVar:-local}", jsonParser)));
}
}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/PropertiesInterpolatorTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/PropertiesInterpolatorTest.java
new file mode 100644
index 000000000..15e5f3741
--- /dev/null
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/internals/jackson/deserializers/interpolation/interpolators/PropertiesInterpolatorTest.java
@@ -0,0 +1,31 @@
+package io.github.giulong.spectrum.internals.jackson.deserializers.interpolation.interpolators;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+
+class PropertiesInterpolatorTest {
+
+ private final String property = "property";
+
+ @InjectMocks
+ private PropertiesInterpolator interpolator;
+
+ @AfterEach
+ void afterEach() {
+ System.clearProperty(property);
+ }
+
+ @Test
+ @DisplayName("getConsumer should return System::getProperty")
+ void getConsumer() {
+ final String value = "value";
+
+ System.setProperty(property, value);
+
+ assertEquals(value, interpolator.getConsumer().apply(property));
+ }
+}
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/utils/ReflectionsTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/utils/ReflectionsTest.java
index 627d74396..5bd97c2eb 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/utils/ReflectionsTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/utils/ReflectionsTest.java
@@ -43,6 +43,17 @@ static Stream valuesProvider() {
arguments(Dummy.class, Object.class, List.of("fieldString", "secured", "parentField")));
}
+ @Test
+ @DisplayName("getFieldsValueOf should return all the fields of the provided object of the provided type")
+ void getFieldsValueOf() {
+ final String fieldName = "fieldString";
+ final String secured = "secured";
+ final Dummy dummy = new Dummy(fieldName, secured, null);
+
+ final List values = Reflections.getFieldsValueOf(dummy);
+ assertEquals(List.of(fieldName, secured), values);
+ }
+
@Test
@DisplayName("getField should return the field with the provided name on the provided object")
void getField() throws NoSuchFieldException {
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/utils/YamlUtilsTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/utils/YamlUtilsTest.java
index b5ea033d0..c9f7dba48 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/utils/YamlUtilsTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/utils/YamlUtilsTest.java
@@ -24,6 +24,8 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
@@ -50,6 +52,9 @@ class YamlUtilsTest {
@Mock
private FileProvider fileProvider;
+ @Captor
+ private ArgumentCaptor inputStreamArgumentCaptor;
+
@InjectMocks
private YamlUtils yamlUtils;
@@ -173,6 +178,25 @@ void readDynamicDeserializable() throws IOException {
assertEquals(testYaml, yamlUtils.readDynamicDeserializable("test.yaml", clazz, jsonNode));
}
+ @Test
+ @DisplayName("updateNode should update the provided object with the content of the node at the provided file")
+ void updateNode() throws IOException {
+ final String file = "test.yaml";
+ final String node = "node";
+
+ Reflections.setField("yamlMapper", yamlUtils, yamlMapper);
+ Reflections.setField("clientFileProvider", yamlUtils, fileProvider);
+
+ when(fileProvider.find(file)).thenReturn(file);
+ when(fileProvider.augment(yamlMapper)).thenReturn(reader);
+ when(reader.withValueToUpdate(testYaml)).thenReturn(reader);
+ when(yamlMapper.readTree(inputStreamArgumentCaptor.capture())).thenReturn(jsonNode);
+ when(jsonNode.at(node)).thenReturn(jsonNode);
+ when(reader.readValue(jsonNode)).thenReturn(testYaml);
+
+ yamlUtils.updateNode(testYaml, node, file, fileProvider);
+ }
+
@DisplayName("updateWithFile should update the provided instance with the file provided, reading only public fields")
@ParameterizedTest(name = "with file {0}")
@ValueSource(strings = {"test.yaml", "test.yml", "configurations/test.yaml"})
@@ -183,7 +207,7 @@ void updateWithFile(final String file) throws IOException {
when(fileProvider.find(file)).thenReturn(file);
when(fileProvider.augment(yamlMapper)).thenReturn(reader);
when(reader.withValueToUpdate(testYaml)).thenReturn(reader);
- when(reader.readValue(any(InputStream.class))).thenReturn(testYaml);
+ when(reader.readValue(inputStreamArgumentCaptor.capture())).thenReturn(testYaml);
yamlUtils.updateWithClientFile(testYaml, file);
}
@@ -217,6 +241,44 @@ void updateWithInternalFile() throws IOException {
yamlUtils.updateWithInternalFile(testYaml, file);
}
+ @Test
+ @DisplayName("updateWithInternalNode should update the provided instance with the node of the internal file provided")
+ void updateWithInternalNode() throws IOException {
+ final String file = "test.yaml";
+ final String node = "node";
+
+ Reflections.setField("yamlMapper", yamlUtils, yamlMapper);
+ Reflections.setField("internalFileProvider", yamlUtils, fileProvider);
+
+ when(fileProvider.find(file)).thenReturn(file);
+ when(fileProvider.augment(yamlMapper)).thenReturn(reader);
+ when(reader.withValueToUpdate(testYaml)).thenReturn(reader);
+ when(yamlMapper.readTree(inputStreamArgumentCaptor.capture())).thenReturn(jsonNode);
+ when(jsonNode.at(node)).thenReturn(jsonNode);
+ when(reader.readValue(jsonNode)).thenReturn(testYaml);
+
+ yamlUtils.updateWithInternalNode(testYaml, node, file);
+ }
+
+ @Test
+ @DisplayName("updateWithClientNode should update the provided instance with the node of the client file provided")
+ void updateWithClientNode() throws IOException {
+ final String file = "test.yaml";
+ final String node = "node";
+
+ Reflections.setField("yamlMapper", yamlUtils, yamlMapper);
+ Reflections.setField("clientFileProvider", yamlUtils, fileProvider);
+
+ when(fileProvider.find(file)).thenReturn(file);
+ when(fileProvider.augment(yamlMapper)).thenReturn(reader);
+ when(reader.withValueToUpdate(testYaml)).thenReturn(reader);
+ when(yamlMapper.readTree(inputStreamArgumentCaptor.capture())).thenReturn(jsonNode);
+ when(jsonNode.at(node)).thenReturn(jsonNode);
+ when(reader.readValue(jsonNode)).thenReturn(testYaml);
+
+ yamlUtils.updateWithClientNode(testYaml, node, file);
+ }
+
@Test
@DisplayName("write should just call the writeValueAsString of the provided object, printing internal fields as well")
void write() throws JsonProcessingException {
diff --git a/spectrum/src/test/java/io/github/giulong/spectrum/utils/file_providers/FileProviderTest.java b/spectrum/src/test/java/io/github/giulong/spectrum/utils/file_providers/FileProviderTest.java
index c7797cbae..e2db50214 100644
--- a/spectrum/src/test/java/io/github/giulong/spectrum/utils/file_providers/FileProviderTest.java
+++ b/spectrum/src/test/java/io/github/giulong/spectrum/utils/file_providers/FileProviderTest.java
@@ -33,9 +33,9 @@ class FileProviderTest {
@Test
@DisplayName("augment should return the reader augmented with views and injectable values from the provided mapper")
void augment() {
+ when(mapper.setInjectableValues(std)).thenReturn(mapper);
when(mapper.reader()).thenReturn(reader);
when(reader.withView(Views.Client.class)).thenReturn(reader);
- when(reader.with(std)).thenReturn(reader);
assertEquals(reader, fileProvider.augment(mapper));
}