diff --git a/build.gradle b/build.gradle index 0318854..80d8e7c 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,7 @@ dependencies { testImplementation 'commons-io:commons-io:2.11.0' testImplementation 'org.mockito:mockito-core:4.4.0' testImplementation 'com.squareup.okhttp3:mockwebserver:4.9.3' + testImplementation 'uk.org.webcompere:system-stubs-testng:2.1.3' } test { diff --git a/src/main/java/io/visual_regression_tracker/sdk_java/VisualRegressionTracker.java b/src/main/java/io/visual_regression_tracker/sdk_java/VisualRegressionTracker.java index 7242d64..3c3f491 100755 --- a/src/main/java/io/visual_regression_tracker/sdk_java/VisualRegressionTracker.java +++ b/src/main/java/io/visual_regression_tracker/sdk_java/VisualRegressionTracker.java @@ -7,6 +7,7 @@ import io.visual_regression_tracker.sdk_java.response.TestRunResponse; import lombok.extern.slf4j.Slf4j; +import java.io.File; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; @@ -24,6 +25,7 @@ enum METHOD { public class VisualRegressionTracker { private static final String TRACKER_NOT_STARTED = "Visual Regression Tracker has not been started"; + private static final String CONFIG_FILE_NAME = "vrt.json"; protected static final String API_KEY_HEADER = "apiKey"; protected static final String PROJECT_HEADER = "project"; protected Gson gson; @@ -32,6 +34,17 @@ public class VisualRegressionTracker { protected String buildId; protected String projectId; + public VisualRegressionTracker() { + VisualRegressionTrackerConfig.VisualRegressionTrackerConfigBuilder configBuilder = VisualRegressionTrackerConfig.builder(); + File configFile = new File(CONFIG_FILE_NAME); + if (configFile.exists()) { + configBuilder.configFile(configFile); + } + configuration = configBuilder.build(); + paths = new PathProvider(configuration.getApiUrl()); + gson = new Gson(); + } + public VisualRegressionTracker(VisualRegressionTrackerConfig trackerConfig) { configuration = trackerConfig; paths = new PathProvider(trackerConfig.getApiUrl()); diff --git a/src/main/java/io/visual_regression_tracker/sdk_java/VisualRegressionTrackerConfig.java b/src/main/java/io/visual_regression_tracker/sdk_java/VisualRegressionTrackerConfig.java index ccba92c..3b02db4 100644 --- a/src/main/java/io/visual_regression_tracker/sdk_java/VisualRegressionTrackerConfig.java +++ b/src/main/java/io/visual_regression_tracker/sdk_java/VisualRegressionTrackerConfig.java @@ -1,15 +1,26 @@ package io.visual_regression_tracker.sdk_java; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; - -@Data -@Builder +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import lombok.*; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Collections; +import java.util.Map; +import java.util.function.Function; + +@Data() @RequiredArgsConstructor @AllArgsConstructor +@Accessors(chain = true) +@Slf4j public class VisualRegressionTrackerConfig { @NonNull @@ -18,12 +29,145 @@ public class VisualRegressionTrackerConfig { private final String apiKey; @NonNull private final String project; - @Builder.Default - private String branchName = null; - @Builder.Default - private String ciBuildId = null; - @Builder.Default - private Boolean enableSoftAssert = false; - @Builder.Default - private int httpTimeoutInSeconds = 10; + + private String branchName; + private String ciBuildId; + private Boolean enableSoftAssert; + private int httpTimeoutInSeconds; + + public static VisualRegressionTrackerConfigBuilder builder() { + return new VisualRegressionTrackerConfigBuilder(); + } + + public static class VisualRegressionTrackerConfigBuilder { + private String apiUrl; + private String apiKey; + private String project; + + private String branchName; + private String ciBuildId; + private Boolean enableSoftAssert; + private Integer httpTimeoutInSeconds; + + private File configFile; + + private static final String VRT_ENV_VARIABLE_PREFIX = "VRT_"; + private static final boolean DEFAULT_SOFT_ASSERTION_STATE = false; + private static final int DEFAULT_HTTP_TIMEOUT_SECONDS = 10; + + public VisualRegressionTrackerConfigBuilder apiUrl(String apiUrl) { + this.apiUrl = apiUrl; + return this; + } + + public VisualRegressionTrackerConfigBuilder apiKey(String apiKey) { + this.apiKey = apiKey; + return this; + } + + public VisualRegressionTrackerConfigBuilder project(String project) { + this.project = project; + return this; + } + + public VisualRegressionTrackerConfigBuilder branchName(String branchName) { + this.branchName = branchName; + return this; + } + + public VisualRegressionTrackerConfigBuilder ciBuildId(String ciBuildId) { + this.ciBuildId = ciBuildId; + return this; + } + + public VisualRegressionTrackerConfigBuilder enableSoftAssert(Boolean enableSoftAssert) { + this.enableSoftAssert = enableSoftAssert; + return this; + } + + public VisualRegressionTrackerConfigBuilder httpTimeoutInSeconds(int httpTimeoutInSeconds) { + this.httpTimeoutInSeconds = httpTimeoutInSeconds; + return this; + } + + public VisualRegressionTrackerConfigBuilder configFile(File configFile) { + this.configFile = configFile; + return this; + } + + public VisualRegressionTrackerConfig build() { + Map configFromFile = Collections.emptyMap(); + if (configFile != null) { + configFromFile = readConfigFromFile(configFile); + } + + String actualApiUrl = resolve("apiUrl", configFromFile); + String actualApiKey = resolve("apiKey", configFromFile); + String actualProject = resolve("project", configFromFile); + + VisualRegressionTrackerConfig config = new VisualRegressionTrackerConfig(actualApiUrl, actualApiKey, actualProject); + config.setCiBuildId(resolve("ciBuildId", configFromFile)); + config.setBranchName(resolve("branchName", configFromFile)); + + Boolean actualEnableSoftAssert = resolve("enableSoftAssert", configFromFile); + config.setEnableSoftAssert(actualEnableSoftAssert == null ? DEFAULT_SOFT_ASSERTION_STATE : actualEnableSoftAssert); + + Integer actualHttpTimeoutInSeconds = resolve("httpTimeoutInSeconds", configFromFile); + config.setHttpTimeoutInSeconds(actualHttpTimeoutInSeconds == null ? DEFAULT_HTTP_TIMEOUT_SECONDS : actualHttpTimeoutInSeconds); + + return config; + } + + private Map readConfigFromFile(File configFile) { + if (!configFile.exists()) { + throw new IllegalArgumentException("File " + configFile + " doesn't exist"); + } + + String fileContent; + try { + fileContent = Files.readString(configFile.toPath(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new IllegalArgumentException("Can't read content of provided config file", e); + } + Type mapType = new TypeToken>() {}.getType(); + return new Gson().fromJson(fileContent, mapType); + } + + @SneakyThrows + private T resolve(String propertyName, Map configurationFromFile) { + // 1. check if it was initialized explicitly in builder + // 2. check if env variable exists + // 3. try to read from file as last resort + Field field = this.getClass().getDeclaredField(propertyName); + Object propertyValue = field.get(this); + if (propertyValue != null) { + return (T) propertyValue; + } + + String environmentVariableName = VRT_ENV_VARIABLE_PREFIX + propertyName.toUpperCase(); + propertyValue = System.getenv(environmentVariableName); + if (propertyValue != null) { + log.debug("Value of '{}' resolved from environment variable {}", propertyName, environmentVariableName); + Function parser = findParser(field.getType()); + return (T) parser.apply((String)propertyValue); + } + + propertyValue = configurationFromFile.get(propertyName); + if (propertyValue != null) { + log.debug("Value of '{}' resolved from config file", propertyName); + } + return propertyValue == null ? null : (T) propertyValue; + } + + private Function findParser(Class cls) { + if (cls.equals(Boolean.class)) { + return Boolean::parseBoolean; + } + if (cls.equals(Integer.class)) { + return Integer::parseInt; + } + return String::valueOf; + } + } + } diff --git a/src/test/java/io/visual_regression_tracker/sdk_java/VisualRegressionTrackerConfigTest.java b/src/test/java/io/visual_regression_tracker/sdk_java/VisualRegressionTrackerConfigTest.java new file mode 100644 index 0000000..dd6e2c3 --- /dev/null +++ b/src/test/java/io/visual_regression_tracker/sdk_java/VisualRegressionTrackerConfigTest.java @@ -0,0 +1,114 @@ +package io.visual_regression_tracker.sdk_java; + +import org.testng.annotations.Test; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; + +import java.io.File; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class VisualRegressionTrackerConfigTest { + + @Test(expectedExceptions = NullPointerException.class, + expectedExceptionsMessageRegExp = "apiKey is marked non-null but is null") + public void shouldThrowExceptionIfApiKeyMissed() { + VisualRegressionTrackerConfig + .builder() + .apiUrl("") + .project("") + .build(); + } + + @Test(expectedExceptions = NullPointerException.class, + expectedExceptionsMessageRegExp = "apiUrl is marked non-null but is null") + public void shouldThrowExceptionIfApiUrlMissed() { + VisualRegressionTrackerConfig + .builder() + .apiKey("") + .project("") + .build(); + } + + @Test(expectedExceptions = NullPointerException.class, + expectedExceptionsMessageRegExp = "project is marked non-null but is null") + public void shouldThrowExceptionIfProjectMissed() { + VisualRegressionTrackerConfig + .builder() + .apiKey("") + .apiUrl("") + .build(); + } + + @Test + public void shouldBeCreatedFromConfigFile() { + File configFile = new File("src/test/resources/vrt_config_example.json"); + + VisualRegressionTrackerConfig config = VisualRegressionTrackerConfig.builder() + .configFile(configFile) + .build(); + + assertThat(config.getApiKey(), is("SECRET")); + assertThat(config.getApiUrl(), is("http://162.243.161.172:4200")); + assertThat(config.getProject(), is("VRT")); + assertThat(config.getBranchName(), is("master")); + assertThat(config.getEnableSoftAssert(), is(false)); + assertThat(config.getCiBuildId(), is("SOME_UNIQUE_ID")); + } + + @Test + public void shouldBeCreatedFromEnvironment() throws Exception { + EnvironmentVariables environmentVariables = new EnvironmentVariables("VRT_APIKEY", "SECRET") + .set("VRT_APIURL", "http://162.243.161.172:4200") + .set("VRT_PROJECT", "VRT") + .set("VRT_BRANCHNAME", "master") + .set("VRT_ENABLESOFTASSERT", "false") + .set("VRT_CIBUILDID", "SOME_UNIQUE_ID"); + + VisualRegressionTrackerConfig config = environmentVariables.execute(() -> + VisualRegressionTrackerConfig.builder() + .build() + ); + + assertThat(config.getApiKey(), is("SECRET")); + assertThat(config.getApiUrl(), is("http://162.243.161.172:4200")); + assertThat(config.getProject(), is("VRT")); + assertThat(config.getBranchName(), is("master")); + assertThat(config.getEnableSoftAssert(), is(false)); + assertThat(config.getCiBuildId(), is("SOME_UNIQUE_ID")); + } + + @Test + public void shouldResolveFinalValuesInTheRightOrder() throws Exception { + EnvironmentVariables environmentVariables = new EnvironmentVariables("VRT_APIKEY", "ENV_SECRET") + .set("VRT_APIURL", "http://162.243.161.172:4201"); + File configFile = new File("src/test/resources/vrt_config_example.json"); + + VisualRegressionTrackerConfig config = environmentVariables.execute(() -> + VisualRegressionTrackerConfig.builder() + .apiUrl("http://localhost:4200") + .configFile(configFile) + .build() + ); + + assertThat(config.getApiKey(), is("ENV_SECRET")); + assertThat(config.getApiUrl(), is("http://localhost:4200")); + assertThat(config.getProject(), is("VRT")); + assertThat(config.getBranchName(), is("master")); + assertThat(config.getEnableSoftAssert(), is(false)); + assertThat(config.getCiBuildId(), is("SOME_UNIQUE_ID")); + } + + + @Test + public void shouldUseDefaultValuesIfNotProvided() { + VisualRegressionTrackerConfig config = VisualRegressionTrackerConfig.builder() + .apiUrl("http://localhost:4200") + .apiKey("KEY") + .project("PROJECT") + .build(); + + assertThat(config.getEnableSoftAssert(), is(false)); + assertThat(config.getHttpTimeoutInSeconds(), is(10)); + } +} diff --git a/src/test/resources/vrt_config_example.json b/src/test/resources/vrt_config_example.json new file mode 100644 index 0000000..291228d --- /dev/null +++ b/src/test/resources/vrt_config_example.json @@ -0,0 +1,8 @@ +{ + "apiUrl": "http://162.243.161.172:4200", + "project": "VRT", + "apiKey": "SECRET", + "branchName": "master", + "enableSoftAssert": false, + "ciBuildId": "SOME_UNIQUE_ID" +} \ No newline at end of file