Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<String, Object> 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<String, Object> 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<Map<String, Object>>() {}.getType();
return new Gson().fromJson(fileContent, mapType);
}

@SneakyThrows
private <T> T resolve(String propertyName, Map<String, Object> 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<String, ?> 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<String, ?> findParser(Class<?> cls) {
if (cls.equals(Boolean.class)) {
return Boolean::parseBoolean;
}
if (cls.equals(Integer.class)) {
return Integer::parseInt;
}
return String::valueOf;
}
}

}
Original file line number Diff line number Diff line change
@@ -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));
}
}
8 changes: 8 additions & 0 deletions src/test/resources/vrt_config_example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"apiUrl": "http://162.243.161.172:4200",
"project": "VRT",
"apiKey": "SECRET",
"branchName": "master",
"enableSoftAssert": false,
"ciBuildId": "SOME_UNIQUE_ID"
}