Skip to content

Commit

Permalink
'match each' implemented which fixes #1 and fixes #4. 'ssl enabled' i…
Browse files Browse the repository at this point in the history
…mplemented - so will not use fake cert/store by default, other refactoring and doc improvements
  • Loading branch information
ptrthomas committed Feb 19, 2017
1 parent ea711f2 commit a3d6bfc
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 166 deletions.
178 changes: 107 additions & 71 deletions README.md

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions karate-core/src/main/java/com/intuit/karate/MatchType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.intuit.karate;

/**
*
* @author pthomas3
*/
public enum MatchType {

EQUALS,
CONTAINS,
EACH_EQUALS,
EACH_CONTAINS

}
87 changes: 56 additions & 31 deletions karate-core/src/main/java/com/intuit/karate/Script.java
Original file line number Diff line number Diff line change
Expand Up @@ -340,10 +340,10 @@ public static boolean isQuoted(String exp) {
}

public static AssertionResult matchNamed(String name, String path, String expected, ScriptContext context) {
return matchNamed(false, name, path, expected, context);
return matchNamed(MatchType.EQUALS, name, path, expected, context);
}

public static AssertionResult matchNamed(boolean contains, String name, String path, String expected, ScriptContext context) {
public static AssertionResult matchNamed(MatchType matchType, String name, String path, String expected, ScriptContext context) {
name = StringUtils.trim(name);
if (isJsonPath(name) || isXmlPath(name)) { // short-cut for operating on response
path = name;
Expand All @@ -357,39 +357,39 @@ public static AssertionResult matchNamed(boolean contains, String name, String p
}
expected = StringUtils.trim(expected);
if ("header".equals(name)) { // convenience shortcut for asserting against response header
return matchNamed(contains, ScriptValueMap.VAR_RESPONSE_HEADERS, "$['" + path + "'][0]", expected, context);
return matchNamed(matchType, ScriptValueMap.VAR_RESPONSE_HEADERS, "$['" + path + "'][0]", expected, context);
} else {
ScriptValue actual = context.vars.get(name);
switch(actual.getType()) {
switch (actual.getType()) {
case STRING:
case INPUT_STREAM:
return matchString(contains, actual, expected, path, context);
return matchString(matchType, actual, expected, path, context);
case XML:
if ("$".equals(path)) {
path = "/"; // edge case where the name was 'response'
}
if (!isJsonPath(path)) {
return matchXmlPath(contains, actual, path, expected, context);
return matchXmlPath(matchType, actual, path, expected, context);
}
// break;
// fall through to JSON. yes, dot notation can be used on XML
// break;
// fall through to JSON. yes, dot notation can be used on XML !!
default:
return matchJsonPath(contains, actual, path, expected, context);
return matchJsonPath(matchType, actual, path, expected, context);
}
}
}

public static AssertionResult matchString(boolean contains, ScriptValue actual, String expected, String path, ScriptContext context) {
public static AssertionResult matchString(MatchType matchType, ScriptValue actual, String expected, String path, ScriptContext context) {
ScriptValue expectedValue = preEval(expected, context);
expected = expectedValue.getAsString();
return matchStringOrPattern(contains, actual, expected, path, context);
return matchStringOrPattern(matchType, actual, expected, path, context);
}

public static boolean isValidator(String text) {
return text.startsWith("#");
}

public static AssertionResult matchStringOrPattern(boolean contains, ScriptValue actValue, String expected, String path, ScriptContext context) {
public static AssertionResult matchStringOrPattern(MatchType matchType, ScriptValue actValue, String expected, String path, ScriptContext context) {
if (expected == null) {
if (!actValue.isNull()) {
return matchFailed(path, actValue.getValue(), expected);
Expand Down Expand Up @@ -422,7 +422,7 @@ public static AssertionResult matchStringOrPattern(boolean contains, ScriptValue
}
} else {
String actual = actValue.getAsString();
if (contains) {
if (matchType == MatchType.CONTAINS) {
if (!actual.contains(expected)) {
return matchFailed(path, actual, expected + " (not a sub-string)");
}
Expand All @@ -433,11 +433,11 @@ public static AssertionResult matchStringOrPattern(boolean contains, ScriptValue
return AssertionResult.PASS;
}

public static AssertionResult matchXmlObject(boolean contains, Object act, Object exp, ScriptContext context) {
return matchNestedObject('/', "", contains, act, exp, context);
public static AssertionResult matchXmlObject(MatchType matchType, Object act, Object exp, ScriptContext context) {
return matchNestedObject('/', "", matchType, act, exp, context);
}

public static AssertionResult matchXmlPath(boolean contains, ScriptValue actual, String path, String expression, ScriptContext context) {
public static AssertionResult matchXmlPath(MatchType matchType, ScriptValue actual, String path, String expression, ScriptContext context) {
Document actualDoc = actual.getValue(Document.class);
Node actNode = XmlUtils.getNodeByPath(actualDoc, path);
ScriptValue expected = preEval(expression, context);
Expand All @@ -453,10 +453,10 @@ public static AssertionResult matchXmlPath(boolean contains, ScriptValue actual,
actObject = new ScriptValue(actNode).getAsString();
expObject = expected.getAsString();
}
return matchNestedObject('/', path, contains, actObject, expObject, context);
return matchNestedObject('/', path, matchType, actObject, expObject, context);
}

public static AssertionResult matchJsonPath(boolean contains, ScriptValue actual, String path, String expression, ScriptContext context) {
public static AssertionResult matchJsonPath(MatchType matchType, ScriptValue actual, String path, String expression, ScriptContext context) {
DocumentContext actualDoc;
switch (actual.getType()) {
case JSON:
Expand All @@ -476,7 +476,7 @@ public static AssertionResult matchJsonPath(boolean contains, ScriptValue actual
if (!expected.isString()) {
return matchFailed(path, actualString, expected.getValue());
} else {
return matchStringOrPattern(contains, actual, expected.getValue(String.class), path, context);
return matchStringOrPattern(matchType, actual, expected.getValue(String.class), path, context);
}
default:
throw new RuntimeException("not json, cannot do json path for value: " + actual + ", path: " + path);
Expand All @@ -491,15 +491,40 @@ public static AssertionResult matchJsonPath(boolean contains, ScriptValue actual
default:
expObject = expected.getValue();
}
return matchNestedObject('.', path, contains, actObject, expObject, context);
switch (matchType) {
case CONTAINS:
case EQUALS:
return matchNestedObject('.', path, matchType, actObject, expObject, context);
case EACH_EQUALS:
case EACH_CONTAINS:
if (actObject instanceof List) {
List actList = (List) actObject;
MatchType listMatchType = matchType == MatchType.EACH_CONTAINS ? MatchType.CONTAINS : MatchType.EQUALS;
int actSize = actList.size();
for (int i = 0; i < actSize; i++) {
Object actListObject = actList.get(i);
String listPath = path + "[" + i + "]";
AssertionResult ar = matchNestedObject('.', listPath, listMatchType, actListObject, expObject, context);
if (!ar.pass) {
return ar;
}
}
return AssertionResult.PASS;
} else {
throw new RuntimeException("'match all' failed, not a json array: + " + actual + ", path: " + path);
}
default:
// dead code
return AssertionResult.PASS;
}
}

public static AssertionResult matchJsonObject(Object act, Object exp, ScriptContext context) {
return matchNestedObject('.', "$", false, act, exp, context);
return matchNestedObject('.', "$", MatchType.EQUALS, act, exp, context);
}

public static AssertionResult matchJsonObject(boolean contains, Object act, Object exp, ScriptContext context) {
return matchNestedObject('.', "$", contains, act, exp, context);
public static AssertionResult matchJsonObject(MatchType matchType, Object act, Object exp, ScriptContext context) {
return matchNestedObject('.', "$", matchType, act, exp, context);
}

public static AssertionResult matchFailed(String path, Object actObject, Object expObject) {
Expand All @@ -508,7 +533,7 @@ public static AssertionResult matchFailed(String path, Object actObject, Object
return AssertionResult.fail(message);
}

public static AssertionResult matchNestedObject(char delimiter, String path, boolean contains, Object actObject, Object expObject, ScriptContext context) {
public static AssertionResult matchNestedObject(char delimiter, String path, MatchType matchType, Object actObject, Object expObject, ScriptContext context) {
logger.trace("path: {}, actual: '{}', expected: '{}'", path, actObject, expObject);
if (expObject == null) {
if (actObject != null) {
Expand All @@ -518,20 +543,20 @@ public static AssertionResult matchNestedObject(char delimiter, String path, boo
}
if (expObject instanceof String) {
ScriptValue actValue = new ScriptValue(actObject);
return matchStringOrPattern(contains, actValue, expObject.toString(), path, context);
return matchStringOrPattern(matchType, actValue, expObject.toString(), path, context);
} else if (expObject instanceof Map) {
if (!(actObject instanceof Map)) {
return matchFailed(path, actObject, expObject);
}
Map<String, Object> expMap = (Map) expObject;
Map<String, Object> actMap = (Map) actObject;
if (!contains && actMap.size() > expMap.size()) { // > is because of the chance of #ignore
if (matchType != MatchType.CONTAINS && actMap.size() > expMap.size()) { // > is because of the chance of #ignore
return matchFailed(path, actObject, expObject);
}
for (Map.Entry<String, Object> expEntry : expMap.entrySet()) {
String key = expEntry.getKey();
String childPath = path + delimiter + key;
AssertionResult ar = matchNestedObject(delimiter, childPath, Boolean.FALSE, actMap.get(key), expEntry.getValue(), context);
AssertionResult ar = matchNestedObject(delimiter, childPath, MatchType.EQUALS, actMap.get(key), expEntry.getValue(), context);
if (!ar.pass) {
return ar;
}
Expand All @@ -542,16 +567,16 @@ public static AssertionResult matchNestedObject(char delimiter, String path, boo
List actList = (List) actObject;
int actCount = actList.size();
int expCount = expList.size();
if (!contains && actCount != expCount) {
if (matchType != MatchType.CONTAINS && actCount != expCount) {
return matchFailed(path, actObject, expObject);
}
if (contains) { // just checks for existence
if (matchType == MatchType.CONTAINS) { // just checks for existence
for (Object expListObject : expList) { // for each expected item in the list
boolean found = false;
for (int i = 0; i < actCount; i++) {
Object actListObject = actList.get(i);
String listPath = path + "[" + i + "]";
AssertionResult ar = matchNestedObject(delimiter, listPath, Boolean.FALSE, actListObject, expListObject, context);
AssertionResult ar = matchNestedObject(delimiter, listPath, MatchType.EQUALS, actListObject, expListObject, context);
if (ar.pass) { // exact match, we found it
found = true;
break;
Expand All @@ -567,7 +592,7 @@ public static AssertionResult matchNestedObject(char delimiter, String path, boo
Object expListObject = expList.get(i);
Object actListObject = actList.get(i);
String listPath = path + "[" + i + "]";
AssertionResult ar = matchNestedObject(delimiter, listPath, Boolean.FALSE, actListObject, expListObject, context);
AssertionResult ar = matchNestedObject(delimiter, listPath, MatchType.EQUALS, actListObject, expListObject, context);
if (!ar.pass) {
return matchFailed(path, actObject, expObject);
}
Expand Down
51 changes: 29 additions & 22 deletions karate-core/src/main/java/com/intuit/karate/ScriptContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.intuit.karate.validator.Validator;
import java.util.Map;
import java.util.logging.Level;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
Expand All @@ -17,22 +18,22 @@
* @author pthomas3
*/
public class ScriptContext {

private static final Logger logger = LoggerFactory.getLogger(ScriptContext.class);

private static final String KARATE_NAME = "karate";

protected final ScriptValueMap vars;
protected final Client client;
protected Client client;
protected final Map<String, Validator> validators;
protected final String featureDir;
protected final ClassLoader fileClassLoader;
protected final String env;
protected final String env;

// needed for 3rd party code
public ScriptValueMap getVars() {
return vars;
}
}

public ScriptContext(boolean test, String featureDir, ClassLoader fileClassLoader, String env) {
this.featureDir = featureDir;
Expand All @@ -47,36 +48,42 @@ public ScriptContext(boolean test, String featureDir, ClassLoader fileClassLoade
client = null;
return;
}
ClientBuilder clientBuilder = ClientBuilder.newBuilder()
.register(MultiPartFeature.class);
if (logger.isDebugEnabled()) {
clientBuilder.register(new LoggingFeature(
java.util.logging.Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME),
Level.SEVERE,
LoggingFeature.Verbosity.PAYLOAD_TEXT, null));
}
SSLContext sslContext = SslUtils.getSslContext();
clientBuilder.sslContext(sslContext);
clientBuilder.hostnameVerifier((host, session) -> true);
clientBuilder.register(new RequestFilter(this));
client = clientBuilder.build();
// auto config
try {
Script.callAndUpdateVars("read('classpath:karate-config.js')", null, this);
} catch (Exception e) {
logger.warn("start-up configuration failed, missing or bad 'karate-config.js' - {}", e.getMessage());
}
logger.trace("karate context init - initial properties: {}", vars);
buildClient(null);
}


public void buildClient(SSLContext ssl) {
ClientBuilder clientBuilder = ClientBuilder.newBuilder().register(MultiPartFeature.class);
if (logger.isDebugEnabled()) {
clientBuilder.register(new LoggingFeature(
java.util.logging.Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME),
Level.SEVERE,
LoggingFeature.Verbosity.PAYLOAD_TEXT, null));
}
clientBuilder.register(new RequestFilter(this));
if (ssl != null) {
logger.info("ssl enabled, initializing generic trusted certificate / key-store");
HttpsURLConnection.setDefaultSSLSocketFactory(ssl.getSocketFactory());
clientBuilder.sslContext(ssl);
clientBuilder.hostnameVerifier((host, session) -> true);
}
client = clientBuilder.build();
}

public void injectInto(ScriptObjectMirror som) {
som.setMember(KARATE_NAME, new ScriptBridge(this));
// convenience for users, can use 'karate' instead of 'this.karate'
som.eval(String.format("var %s = this.%s", KARATE_NAME, KARATE_NAME));
Map<String, Object> simple = Script.simplify(vars);
for (Map.Entry<String, Object> entry : simple.entrySet()) {
som.put(entry.getKey(), entry.getValue()); // update eval context
}
}
}

}
3 changes: 1 addition & 2 deletions karate-core/src/main/java/com/intuit/karate/SslUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ public void checkClientTrusted(X509Certificate[] chain, String authType) throws
ctx.init(null, certs, new SecureRandom());
} catch (Exception e) {
throw new RuntimeException(e);
}
HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory());
}
return ctx;
}

Expand Down
Loading

0 comments on commit a3d6bfc

Please sign in to comment.