commit 1acf696b28af86493a76f51a94940759ffadfead Author: Sander Hautvast Date: Wed Jun 10 10:24:56 2015 +0200 upload diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fd2648 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/build +/.project +/.classpath +/test-output +/.gradle +/.settings +/bin diff --git a/README.md b/README.md new file mode 100644 index 0000000..8c07539 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +Simple universal mocking tool for http requests + +-set up expectations using json files in $PROJECT/everest directory +-start using gradlew run +-replay \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..bcd7fe9 --- /dev/null +++ b/build.gradle @@ -0,0 +1,46 @@ +buildscript { + repositories { + mavenLocal() + mavenCentral() + } + + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.3.RELEASE") + } +} + +apply plugin: 'java' +apply plugin: 'spring-boot' +apply plugin: 'idea' +apply plugin: 'eclipse' + +repositories{ + mavenCentral() + mavenLocal() +} + + +targetCompatibility=1.7 +sourceCompatibility=1.7 + +configurations { + all*.exclude group: 'commons-logging' //http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/htmlsingle/#overview-not-using-commons-logging + compile.exclude module: "spring-boot-starter-jetty" + compile.exclude module: "hibernate-validator" + compile.exclude module: "spring-jdbc" +} + + +dependencies{ + compile 'org.springframework.boot:spring-boot-starter-web' + compile 'commons-io:commons-io:2.0.1' + compile 'com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.2.1' + + testCompile 'org.testng:testng:6.8' + testCompile "org.springframework:spring-test:4.0.6.RELEASE" + testCompile 'org.mockito:mockito-all:1.9.0' + testCompile 'org.apache.httpcomponents:httpclient:4.4' +} + + +springBoot { mainClass = "nl.wehkamp.everest.App" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..c97a8bd Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..42351f2 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Jun 04 20:58:48 CEST 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-bin.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/public/WEB-INF/mockingServlet-servlet.xml b/public/WEB-INF/mockingServlet-servlet.xml new file mode 100644 index 0000000..4c109da --- /dev/null +++ b/public/WEB-INF/mockingServlet-servlet.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/src/main/java/nl/wehkamp/everest/WebServer.java b/src/main/java/nl/wehkamp/everest/WebServer.java new file mode 100644 index 0000000..0698779 --- /dev/null +++ b/src/main/java/nl/wehkamp/everest/WebServer.java @@ -0,0 +1,53 @@ +package nl.wehkamp.everest; + +import javax.servlet.Servlet; + +import nl.wehkamp.everest.web.MockingServlet; + +import org.springframework.beans.BeansException; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.embedded.ServletRegistrationBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan(basePackages = { "nl.wehkamp.everest.web", "nl.wehkamp.everest.dao", "nl.wehkamp.everest.service" }) +public class WebServer implements ApplicationContextAware { + public static WebServer instance; + + public WebServer() { + instance = this; + } + + private ApplicationContext applicationContext; + + public T getBean(Class claz) { + return applicationContext.getBean(claz); + } + + @Bean + public ServletRegistrationBean servletRegistrationBean() { + return new ServletRegistrationBean(mockingServlet(), "/*"); + } + + @Bean + public Servlet mockingServlet() { + return new MockingServlet(); + } + + public static void main(String[] args) { + WebServer.start(); + } + + public static void start() { + SpringApplication.run(WebServer.class); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } +} diff --git a/src/main/java/nl/wehkamp/everest/dao/RequestResponseJsonFileRepository.java b/src/main/java/nl/wehkamp/everest/dao/RequestResponseJsonFileRepository.java new file mode 100644 index 0000000..fbb0ab1 --- /dev/null +++ b/src/main/java/nl/wehkamp/everest/dao/RequestResponseJsonFileRepository.java @@ -0,0 +1,110 @@ +/* + * Project: Wehkamp everest + * Copyright (c) 2015 Wehkamp B.V. + * + * Versie : $LastChangedRevision: $ + * Datum : $LastChangedDate: $ + * Door : $LastChangedBy: $ + */ +package nl.wehkamp.everest.dao; + +import static java.nio.file.Files.newDirectoryStream; +import static java.nio.file.Files.newInputStream; +import static java.nio.file.Files.newOutputStream; +import static java.nio.file.Paths.get; +import static nl.wehkamp.everest.model.Prediction.fromDto; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import nl.wehkamp.everest.model.Prediction; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +@Component("requestResponseRepository") +public class RequestResponseJsonFileRepository extends RequestResponseMemoryRepository { + private static final Logger log = LoggerFactory.getLogger(RequestResponseJsonFileRepository.class); + + private String dataDirectoryName = System.getProperty("everest.data", "everest_data"); + + private ObjectMapper jackson = new ObjectMapper(); + + public RequestResponseJsonFileRepository() { + createDataDirectoryIfAbsent(); + fillCacheIfEmpty(); + } + + @Override + public void save(Prediction prediction) { + super.save(prediction); + OutputStream os = null; + try { + os = trySaveJson(prediction); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + os.close(); + } catch (IOException e) { + } + } + } + + private OutputStream trySaveJson(Prediction record) throws IOException, JsonGenerationException, JsonMappingException { + OutputStream os; + String filename = record.getName() + ".json"; + os = newOutputStream(get(dataDirectoryName, filename)); + jackson.writeValue(os, record.toDto()); + return os; + } + + private void fillCacheIfEmpty() { + if (cache.isEmpty()) { + readDataFilesIntoMemory(); + } + } + + private void readDataFilesIntoMemory() { + try { + DirectoryStream directoryStream = newDirectoryStream(get(dataDirectoryName)); + for (Path pathForJsonFile : directoryStream) { + Prediction requestResponse = readRequestResponseFromJson(pathForJsonFile); + requestResponse.setName(filename(pathForJsonFile)); + cache.add(requestResponse); + log.info("reading {} into memory, mapping path {}", pathForJsonFile, requestResponse.getUrl()); + } + } catch (IOException x) { + throw new RuntimeException(x); + } + } + + private String filename(Path path) { + return path.getFileName().toString(); + } + + private Prediction readRequestResponseFromJson(Path path) throws IOException, JsonParseException, JsonMappingException { + return fromDto(jackson.readValue(newInputStream(path), Prediction.Dto.class)); + } + + private void createDataDirectoryIfAbsent() { + try { + if (!Files.exists(Paths.get(dataDirectoryName))) { + Files.createDirectory(Paths.get(dataDirectoryName)); + } + System.out.println(Paths.get(dataDirectoryName).toFile().getAbsolutePath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/nl/wehkamp/everest/dao/RequestResponseMemoryRepository.java b/src/main/java/nl/wehkamp/everest/dao/RequestResponseMemoryRepository.java new file mode 100644 index 0000000..84b8cc7 --- /dev/null +++ b/src/main/java/nl/wehkamp/everest/dao/RequestResponseMemoryRepository.java @@ -0,0 +1,27 @@ +package nl.wehkamp.everest.dao; + +import java.util.HashSet; +import java.util.Set; + +import nl.wehkamp.everest.model.Prediction; + +import org.springframework.stereotype.Repository; + +@Repository("requestResponseMemoryRepository") +public class RequestResponseMemoryRepository implements RequestResponseRepository { + protected Set cache = new HashSet<>(); + + @Override + public Set findAll() { + return new HashSet<>(cache); + } + + @Override + public void save(Prediction record) { + cache.add(record); + } + + public void clear() { + cache.clear(); + } +} diff --git a/src/main/java/nl/wehkamp/everest/dao/RequestResponseRepository.java b/src/main/java/nl/wehkamp/everest/dao/RequestResponseRepository.java new file mode 100644 index 0000000..7f386ca --- /dev/null +++ b/src/main/java/nl/wehkamp/everest/dao/RequestResponseRepository.java @@ -0,0 +1,14 @@ +package nl.wehkamp.everest.dao; + +import java.util.Set; + +import nl.wehkamp.everest.model.Prediction; + +public interface RequestResponseRepository { + + void save(Prediction record); + + Set findAll(); + + void clear(); +} diff --git a/src/main/java/nl/wehkamp/everest/model/Headers.java b/src/main/java/nl/wehkamp/everest/model/Headers.java new file mode 100644 index 0000000..a0cdc2b --- /dev/null +++ b/src/main/java/nl/wehkamp/everest/model/Headers.java @@ -0,0 +1,76 @@ +package nl.wehkamp.everest.model; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +public class Headers { + private Map> headers = new HashMap>(); + + public Headers() { + } + + public Headers(Map> headers) { + this.headers = headers; + } + + public boolean areSet() { + return !headers.isEmpty(); + } + + /** + * Returns header value for given name or null if none found. + */ + public String get(String name) { + List list = headers.get(name); + if (list.isEmpty()) { + return null; + } else { + return list.get(0); + } + } + + /** + * returns all header values for a given name. Or null if none set + */ + public List getAll(String name) { + return headers.get(name); + } + + public Map> getContent() { + return headers; + } + + public void add(String name, String value) { + List list = headers.get(name); + if (list == null) { + list = new ArrayList(); + headers.put(name, list); + } + list.add(value); + } + + public boolean satisfies(HttpServletRequest request) { + for (Map.Entry> header : headers.entrySet()) { + List headersInRequest = getHeaderValues(request, header.getKey()); + List headersInExpectation = header.getValue(); + if (headersInExpectation.containsAll(headersInRequest)) { + return true; + } + } + return false; + } + + private List getHeaderValues(HttpServletRequest request, String name) { + List requestHeaderValues = new ArrayList(); + for (Enumeration requestheaderlist = request.getHeaders(name); requestheaderlist.hasMoreElements();) { + requestHeaderValues.add(requestheaderlist.nextElement()); + } + return requestHeaderValues; + } + +} diff --git a/src/main/java/nl/wehkamp/everest/model/Prediction.java b/src/main/java/nl/wehkamp/everest/model/Prediction.java new file mode 100644 index 0000000..907b01f --- /dev/null +++ b/src/main/java/nl/wehkamp/everest/model/Prediction.java @@ -0,0 +1,241 @@ +package nl.wehkamp.everest.model; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import nl.wehkamp.everest.util.Uuids; + +public class Prediction { + private Pattern urlPattern; + + private String id; + private String name; + + private String url; + private String method; + private Headers requestHeaders; + + private String response; + private int responseStatus; + + public boolean requestMatches(String requesturl) { + return urlPattern.matcher(requesturl).matches(); + } + + public Prediction() { + id = Uuids.create(); + } + + private Prediction(Dto predictionDto) { + this.id = predictionDto.id; + this.name = predictionDto.name; + setUrl(predictionDto.url); + this.method = predictionDto.method; + this.requestHeaders = new Headers(predictionDto.requestHeaders); + this.response = predictionDto.response; + this.responseStatus = predictionDto.responseStatus; + } + + public String getMethod() { + return method; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + this.urlPattern = Pattern.compile(url); + } + + public void setMethod(String method) { + this.method = method; + } + + public Headers getHeaders() { + if (requestHeaders == null) { + requestHeaders = new Headers(); + } + return requestHeaders; + } + + public void setHeaders(Headers headers) { + this.requestHeaders = headers; + } + + public String getResponse() { + return response; + } + + public void setResponse(String response) { + this.response = response; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getResponseStatus() { + return responseStatus; + } + + public void setResponseStatus(int responseStatus) { + this.responseStatus = responseStatus; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Prediction other = (Prediction) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + + public boolean containsHeaders() { + return requestHeaders != null && requestHeaders.areSet(); + } + + @Override + public String toString() { + return "RequestResponse [url=" + url + ", method=" + method + ", headers=" + requestHeaders + "]"; + } + + public Dto toDto() { + return new Dto(id, name, url, method, requestHeaders.getContent(), response, responseStatus); + } + + public static Prediction fromDto(Dto predictionDto) { + return new Prediction(predictionDto); + } + + public static class Dto { + private String id; + private String name; + + private String url; + private String method; + private Map> requestHeaders; + + private String response; + private int responseStatus; + + public Dto() { + } + + private Dto(String id, String name, String url, String method, Map> requestHeaders, String response, int responseStatus) { + super(); + this.id = id; + this.name = name; + this.url = url; + this.method = method; + this.requestHeaders = requestHeaders; + this.response = response; + this.responseStatus = responseStatus; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public Map> getRequestHeaders() { + return requestHeaders; + } + + public void setRequestHeaders(Map> requestHeaders) { + this.requestHeaders = requestHeaders; + } + + public String getResponse() { + return response; + } + + public void setResponse(String response) { + this.response = response; + } + + public int getResponseStatus() { + return responseStatus; + } + + public void setResponseStatus(int responseStatus) { + this.responseStatus = responseStatus; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Dto other = (Dto) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + + } +} diff --git a/src/main/java/nl/wehkamp/everest/service/ResponseFinder.java b/src/main/java/nl/wehkamp/everest/service/ResponseFinder.java new file mode 100644 index 0000000..4db7b83 --- /dev/null +++ b/src/main/java/nl/wehkamp/everest/service/ResponseFinder.java @@ -0,0 +1,68 @@ +/* + * Project: Wehkamp everest + * Copyright (c) 2015 Wehkamp B.V. + * + * Versie : $LastChangedRevision: $ + * Datum : $LastChangedDate: $ + * Door : $LastChangedBy: $ + */ +package nl.wehkamp.everest.service; + +import java.util.Optional; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; + +import nl.wehkamp.everest.dao.RequestResponseRepository; +import nl.wehkamp.everest.model.Prediction; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * Matches incoming requests against setup predictions + */ +@Service +public class ResponseFinder { + @Autowired + private RequestResponseRepository requestResponseRepository; + + public Optional find(HttpServletRequest request) { + Optional response = Optional.empty(); + + Set all = requestResponseRepository.findAll(); + for (Prediction prediction : all) { + response = matchUrlAndMethodAndHeaders(request, response, prediction); + } + return response; + } + + private Optional matchUrlAndMethodAndHeaders(HttpServletRequest request, Optional response, Prediction rr) { + if (rr.requestMatches(request.getPathInfo())) { + response = matchMethodAndHeaders(request, response, rr); + } + return response; + } + + private Optional matchMethodAndHeaders(HttpServletRequest request, Optional response, Prediction rr) { + if (rr.getMethod().equals(request.getMethod())) { + if (rr.containsHeaders()) { + response = matchHeaders(request, response, rr); + } else { + response = Optional.of(rr); + } + } + return response; + } + + private Optional matchHeaders(HttpServletRequest request, Optional response, Prediction prediction) { + if (prediction.getHeaders().satisfies(request)) { + response = Optional.of(prediction); + } + return response; + } + + public void setRequestResponseRepository(RequestResponseRepository requestResponseRepository) { + this.requestResponseRepository = requestResponseRepository; + } +} diff --git a/src/main/java/nl/wehkamp/everest/util/Uuids.java b/src/main/java/nl/wehkamp/everest/util/Uuids.java new file mode 100644 index 0000000..a8d1015 --- /dev/null +++ b/src/main/java/nl/wehkamp/everest/util/Uuids.java @@ -0,0 +1,24 @@ +package nl.wehkamp.everest.util; + +import java.util.Optional; +import java.util.UUID; + +public class Uuids { + private static Optional testValue = Optional.empty(); + + public static String create() { + if (!testValue.isPresent()) { + return UUID.randomUUID().toString(); + } else { + return testValue.get(); + } + } + + public static void removeTestValue() { + testValue = Optional.empty(); + } + + public static void setTestValue(String testValue) { + Uuids.testValue = Optional.of(testValue); + } +} diff --git a/src/main/java/nl/wehkamp/everest/web/MockingServlet.java b/src/main/java/nl/wehkamp/everest/web/MockingServlet.java new file mode 100644 index 0000000..c620092 --- /dev/null +++ b/src/main/java/nl/wehkamp/everest/web/MockingServlet.java @@ -0,0 +1,40 @@ +package nl.wehkamp.everest.web; + +import java.io.IOException; +import java.util.Optional; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import nl.wehkamp.everest.model.Prediction; +import nl.wehkamp.everest.service.ResponseFinder; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * Mocks http requests + * + */ +@SuppressWarnings("serial") +@Controller +public class MockingServlet extends DispatcherServlet { + @Autowired + private ResponseFinder responseFinder; + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + Optional someRequestResponse = responseFinder.find(req); + if (someRequestResponse.isPresent()) { + Prediction requestResponse = someRequestResponse.get(); + resp.getWriter().print(requestResponse.getResponse()); + resp.setStatus(requestResponse.getResponseStatus()); + } else { + resp.getWriter().print("Not found"); + resp.setStatus(404); + } + + } +} diff --git a/src/test/java/nl/wehkamp/everest/dao/RecordRepositoryTest.java b/src/test/java/nl/wehkamp/everest/dao/RecordRepositoryTest.java new file mode 100644 index 0000000..69b4625 --- /dev/null +++ b/src/test/java/nl/wehkamp/everest/dao/RecordRepositoryTest.java @@ -0,0 +1,51 @@ +package nl.wehkamp.everest.dao; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +import nl.wehkamp.everest.model.Headers; +import nl.wehkamp.everest.model.Prediction; +import nl.wehkamp.everest.util.Uuids; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +public class RecordRepositoryTest { + + @AfterMethod + public void teardown() throws IOException { + Uuids.removeTestValue(); + Files.delete(Paths.get("everest_data", "wehkamp.nl.json")); + } + + @Test + public void saveShouldWriteCorrectJson() throws IOException { + Uuids.setTestValue("91f83cd9-a0a5-49f5-b740-78ba8f504797"); + + Prediction record = new Prediction(); + record.setName("wehkamp.nl"); + record.setUrl("http://www.wehkamp.nl"); + record.setMethod("GET"); + Headers headers = new Headers(); + headers.add("Accept", "application/json"); + record.setHeaders(headers); + record.setResponseStatus(200); + record.setResponse(""); + new RequestResponseJsonFileRepository().save(record); + + List lines = Files.readAllLines(Paths.get("everest_data", "wehkamp.nl.json"), StandardCharsets.UTF_8); + + assertFalse(lines.isEmpty()); + System.out.println(lines.get(0)); + + assertEquals( + lines.get(0), + "{\"id\":\"91f83cd9-a0a5-49f5-b740-78ba8f504797\",\"name\":\"wehkamp.nl\",\"url\":\"http://www.wehkamp.nl\",\"method\":\"GET\",\"requestHeaders\":{\"Accept\":[\"application/json\"]},\"response\":\"\",\"responseStatus\":200}"); + } +} diff --git a/src/test/java/nl/wehkamp/everest/web/MockingServletTests.java b/src/test/java/nl/wehkamp/everest/web/MockingServletTests.java new file mode 100644 index 0000000..6305623 --- /dev/null +++ b/src/test/java/nl/wehkamp/everest/web/MockingServletTests.java @@ -0,0 +1,92 @@ +package nl.wehkamp.everest.web; + +import static java.util.Collections.singletonMap; +import static nl.wehkamp.everest.web.TestClient.get; +import static nl.wehkamp.everest.web.TestClient.post; +import static org.testng.Assert.assertEquals; +import nl.wehkamp.everest.WebServer; +import nl.wehkamp.everest.dao.RequestResponseMemoryRepository; +import nl.wehkamp.everest.model.Prediction; +import nl.wehkamp.everest.service.ResponseFinder; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeSuite; +import org.testng.annotations.Test; + +/** + * Executes tests against a running webserver. + */ +public class MockingServletTests { + + private RequestResponseMemoryRepository requestResponseRepository; + + @BeforeSuite + public void setup() { + WebServer.start(); + ResponseFinder responseFinder = WebServer.instance.getBean(ResponseFinder.class); + requestResponseRepository = new RequestResponseMemoryRepository(); + responseFinder.setRequestResponseRepository(requestResponseRepository); + } + + @AfterMethod + public void clearRepository() { + requestResponseRepository.clear(); + } + + @Test + public void testGet() { + Prediction record = new Prediction(); + record.setMethod("GET"); + record.setUrl("/testget"); + record.setResponse("get successful"); + record.setResponseStatus(200); + requestResponseRepository.save(record); + assertEquals(get("/testget"), "200:get successful"); + } + + @Test + public void testGetWithHeader() { + Prediction record = new Prediction(); + record.setMethod("GET"); + record.setUrl("/testget"); + record.getHeaders().add("Content-Type", "application/json"); + record.setResponse("getWithHeader successful"); + record.setResponseStatus(200); + requestResponseRepository.save(record); + assertEquals(get("/testget", singletonMap("Content-Type", "application/json")), "200:getWithHeader successful"); + } + + @Test + public void testGetWithWrongHeader() { + Prediction record = new Prediction(); + record.setMethod("GET"); + record.setUrl("/testget"); + record.getHeaders().add("Content-Type", "application/xml"); + record.setResponse("getWithHeader successful"); + record.setResponseStatus(200); + requestResponseRepository.save(record); + assertEquals(get("/testget", singletonMap("Content-Type", "application/json")), "404:Not found"); + } + + @Test + public void testGetWildcard() { + Prediction record = new Prediction(); + record.setMethod("GET"); + record.setUrl("/testget/.*"); + record.setResponse("get wildcard successful"); + record.setResponseStatus(200); + requestResponseRepository.save(record); + assertEquals(get("/testget/foo"), "200:get wildcard successful"); + } + + @Test + public void testPost() { + Prediction record = new Prediction(); + record.setMethod("POST"); + record.setUrl("/testpost"); + record.setResponse("post successful"); + record.setResponseStatus(200); + requestResponseRepository.save(record); + assertEquals(post("/testpost", "body"), "200:post successful"); + } +} diff --git a/src/test/java/nl/wehkamp/everest/web/TestClient.java b/src/test/java/nl/wehkamp/everest/web/TestClient.java new file mode 100644 index 0000000..cb49730 --- /dev/null +++ b/src/test/java/nl/wehkamp/everest/web/TestClient.java @@ -0,0 +1,53 @@ +package nl.wehkamp.everest.web; + +import java.io.IOException; +import java.util.Map; + +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.HttpClients; + +public class TestClient { + private static HttpClient httpclient = HttpClients.createDefault(); + private static HttpHost target = new HttpHost("localhost", 8080, "http"); + + public static String get(String url) { + try { + HttpGet httpGet = new HttpGet(url); + HttpResponse httpResponse = httpclient.execute(target, httpGet); + String body = IOUtils.toString(httpResponse.getEntity().getContent()); + return httpResponse.getStatusLine().getStatusCode() + ":" + body; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static String get(String url, Map headers) { + HttpGet httpGet = new HttpGet(url); + for (Map.Entry header : headers.entrySet()) { + httpGet.addHeader(header.getKey(), header.getValue()); + } + try { + HttpResponse httpResponse = httpclient.execute(target, httpGet); + String body = IOUtils.toString(httpResponse.getEntity().getContent()); + return httpResponse.getStatusLine().getStatusCode() + ":" + body; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static String post(String url, String postBody) { + try { + HttpPost httpPost = new HttpPost(url); + HttpResponse httpResponse = httpclient.execute(target, httpPost); + String body = IOUtils.toString(httpResponse.getEntity().getContent()); + return httpResponse.getStatusLine().getStatusCode() + ":" + body; + } catch (IOException e) { + throw new RuntimeException(e); + } + } +}