diff --git a/README.md b/README.md
index 7635c7f53..6d1841207 100755
--- a/README.md
+++ b/README.md
@@ -172,7 +172,7 @@ This is all that you need within your ``:
com.intuit.karate
karate-junit4
- 0.2.5
+ 0.2.6
test
```
@@ -193,7 +193,7 @@ You can replace the values of 'com.mycompany' and 'myproject' as per your needs.
mvn archetype:generate \
-DarchetypeGroupId=com.intuit.karate \
-DarchetypeArtifactId=karate-archetype \
--DarchetypeVersion=0.2.5 \
+-DarchetypeVersion=0.2.6 \
-DgroupId=com.mycompany \
-DartifactId=myproject
```
@@ -1493,7 +1493,7 @@ function() {
ip_header: '123.45.67.89',
};
var authString = '';
- var authToken = karate.get('authToken'); // use the 'karate' helper to do a 'safe' get of a variable
+ var authToken = karate.get('authToken'); // use the 'karate' helper to do a 'safe' get of a 'dynamic' variable
if (authToken) { // and if 'authToken' is not null ...
authString = ',auth_type=MyAuthScheme'
+ ',auth_key=' + authToken.key
diff --git a/karate-archetype/pom.xml b/karate-archetype/pom.xml
index d2591cfc6..0b1c64c56 100755
--- a/karate-archetype/pom.xml
+++ b/karate-archetype/pom.xml
@@ -5,7 +5,7 @@
com.intuit.karate
karate-parent
- 0.2.5
+ 0.2.6
karate-archetype
jar
diff --git a/karate-archetype/src/main/resources/archetype-resources/pom.xml b/karate-archetype/src/main/resources/archetype-resources/pom.xml
index 8dba08c9b..fa3aa4dd0 100755
--- a/karate-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/karate-archetype/src/main/resources/archetype-resources/pom.xml
@@ -17,7 +17,7 @@
com.intuit.karate
karate-junit4
- 0.2.5
+ 0.2.6
test
diff --git a/karate-core/pom.xml b/karate-core/pom.xml
index b9ac084fb..7d2c2e95f 100755
--- a/karate-core/pom.xml
+++ b/karate-core/pom.xml
@@ -5,7 +5,7 @@
com.intuit.karate
karate-parent
- 0.2.5
+ 0.2.6
karate-core
jar
diff --git a/karate-core/src/main/java/com/intuit/karate/RequestFilter.java b/karate-core/src/main/java/com/intuit/karate/RequestFilter.java
index a5d9ab13b..812d8f3ff 100755
--- a/karate-core/src/main/java/com/intuit/karate/RequestFilter.java
+++ b/karate-core/src/main/java/com/intuit/karate/RequestFilter.java
@@ -16,16 +16,11 @@
*/
public class RequestFilter implements ClientRequestFilter {
- private static final Logger logger = LoggerFactory.getLogger(RequestFilter.class);
-
- private final ScriptContext context;
-
- public RequestFilter(ScriptContext context) {
- this.context = context;
- }
+ private static final Logger logger = LoggerFactory.getLogger(RequestFilter.class);
@Override
- public void filter(ClientRequestContext ctx) throws IOException {
+ public void filter(ClientRequestContext ctx) throws IOException {
+ ScriptContext context = (ScriptContext) ctx.getProperty(ScriptContext.KARATE_DOT_CONTEXT);
ScriptValue headersFunction = context.headers;
if (headersFunction.getType() != JS_FUNCTION) {
logger.trace("configured 'headers' is not a js function: {}", headersFunction);
diff --git a/karate-core/src/main/java/com/intuit/karate/Script.java b/karate-core/src/main/java/com/intuit/karate/Script.java
index 2ad8235c8..d2e3e903b 100755
--- a/karate-core/src/main/java/com/intuit/karate/Script.java
+++ b/karate-core/src/main/java/com/intuit/karate/Script.java
@@ -266,6 +266,7 @@ public static ScriptValue evalInNashorn(String exp, ScriptContext context, Scrip
for (Map.Entry entry : map.entrySet()) {
bindings.put(entry.getKey(), entry.getValue());
}
+ bindings.put(ScriptContext.KARATE_NAME, new ScriptBridge(context));
}
if (selfValue != null) {
bindings.put(VAR_SELF, selfValue.getValue());
@@ -562,10 +563,15 @@ public static AssertionResult matchJsonPath(MatchType matchType, ScriptValue act
case JSON:
actualDoc = actual.getValue(DocumentContext.class);
break;
+ case JS_ARRAY: // happens for json resulting from nashorn
+ ScriptObjectMirror som = actual.getValue(ScriptObjectMirror.class);
+ actualDoc = JsonPath.parse(som.values());
+ break;
+ case JS_OBJECT: // is a map-like object, happens for json resulting from nashorn
case MAP: // this happens because some jsonpath operations result in Map
Map map = actual.getValue(Map.class);
actualDoc = JsonPath.parse(map);
- break;
+ break;
case LIST: // this also happens because some jsonpath operations result in List
List list = actual.getValue(List.class);
actualDoc = JsonPath.parse(list);
@@ -583,6 +589,8 @@ public static AssertionResult matchJsonPath(MatchType matchType, ScriptValue act
} else {
return matchStringOrPattern('.', path, matchType, null, actual, expected.getValue(String.class), context);
}
+ case PRIMITIVE:
+ return matchPrimitive(path, actual.getValue(), eval(expression, context).getValue());
default:
throw new RuntimeException("not json, cannot do json path for value: " + actual + ", path: " + path);
}
@@ -702,29 +710,33 @@ public static AssertionResult matchNestedObject(char delimiter, String path, Mat
return AssertionResult.PASS; // lists (and order) are identical
}
} else if (ClassUtils.isPrimitiveOrWrapper(expObject.getClass())) {
- if (actObject == null) {
- return matchFailed(path, actObject, expObject, "actual value is null");
- }
- if (!expObject.getClass().equals(actObject.getClass())) {
- // types are not the same, use the JS engine for a lenient equality check
- String exp = actObject + " == " + expObject;
- ScriptValue sv = evalInNashorn(exp, null);
- if (sv.isBooleanTrue()) {
- return AssertionResult.PASS;
- } else {
- return matchFailed(path, actObject, expObject, "not equal");
- }
- }
- if (!expObject.equals(actObject)) {
- return matchFailed(path, actObject, expObject, "not equal");
- } else {
- return AssertionResult.PASS; // primitives, are equal
- }
+ return matchPrimitive(path, actObject, expObject);
} else { // this should never happen
throw new RuntimeException("unexpected type: " + expObject.getClass());
}
}
+ private static AssertionResult matchPrimitive(String path, Object actObject, Object expObject) {
+ if (actObject == null) {
+ return matchFailed(path, actObject, expObject, "actual value is null");
+ }
+ if (!expObject.getClass().equals(actObject.getClass())) {
+ // types are not the same, use the JS engine for a lenient equality check
+ String exp = actObject + " == " + expObject;
+ ScriptValue sv = evalInNashorn(exp, null);
+ if (sv.isBooleanTrue()) {
+ return AssertionResult.PASS;
+ } else {
+ return matchFailed(path, actObject, expObject, "not equal");
+ }
+ }
+ if (!expObject.equals(actObject)) {
+ return matchFailed(path, actObject, expObject, "not equal");
+ } else {
+ return AssertionResult.PASS; // primitives, are equal
+ }
+ }
+
public static void setValueByPath(String name, String path, String exp, ScriptContext context) {
name = StringUtils.trim(name);
path = StringUtils.trimToNull(path);
@@ -810,9 +822,10 @@ public static ScriptValue call(String name, String argString, ScriptContext cont
}
public static ScriptValue evalFunctionCall(ScriptObjectMirror som, Object callArg, ScriptContext context) {
- for (Map.Entry entry : context.getVariableBindings().entrySet()) {
- som.put(entry.getKey(), entry.getValue());
- }
+ // ensure that things like 'karate.get' operate on the latest variable state
+ som.setMember(ScriptContext.KARATE_NAME, new ScriptBridge(context));
+ // convenience for users, can use 'karate' instead of 'this.karate'
+ som.eval(String.format("var %s = this.%s", ScriptContext.KARATE_NAME, ScriptContext.KARATE_NAME));
Object result;
if (callArg != null) {
result = som.call(som, callArg);
diff --git a/karate-core/src/main/java/com/intuit/karate/ScriptBridge.java b/karate-core/src/main/java/com/intuit/karate/ScriptBridge.java
index 17cc71f5b..25c901e90 100755
--- a/karate-core/src/main/java/com/intuit/karate/ScriptBridge.java
+++ b/karate-core/src/main/java/com/intuit/karate/ScriptBridge.java
@@ -19,6 +19,10 @@ public class ScriptBridge {
public ScriptBridge(ScriptContext context) {
this.context = context;
}
+
+ public ScriptContext getContext() {
+ return context;
+ }
public Object read(String fileName) {
ScriptValue sv = FileUtils.readFile(fileName, context);
@@ -30,7 +34,13 @@ public void set(String name, Object o) {
}
public Object get(String exp) {
- ScriptValue sv = Script.eval(exp, context); // even json path expressions will work
+ ScriptValue sv;
+ try {
+ sv = Script.eval(exp, context); // even json path expressions will work
+ } catch (Exception e) {
+ logger.warn("karate.get failed for expression: '{}': {}", exp, e.getMessage());
+ return null;
+ }
if (sv != null) {
return sv.getAfterConvertingToMapIfNeeded();
} else {
diff --git a/karate-core/src/main/java/com/intuit/karate/ScriptContext.java b/karate-core/src/main/java/com/intuit/karate/ScriptContext.java
index dcceb3283..8aa276e54 100755
--- a/karate-core/src/main/java/com/intuit/karate/ScriptContext.java
+++ b/karate-core/src/main/java/com/intuit/karate/ScriptContext.java
@@ -22,8 +22,8 @@ public class ScriptContext {
private static final Logger logger = LoggerFactory.getLogger(ScriptContext.class);
- private static final String KARATE_NAME = "karate";
- private static final String VAR_CONTEXT = "_context";
+ public static final String KARATE_DOT_CONTEXT = "karate.context";
+ public static final String KARATE_NAME = "karate";
private static final String VAR_READ = "read";
protected final ScriptValueMap vars;
@@ -89,7 +89,7 @@ public ScriptContext(ScriptEnv env, ScriptContext parent, Map ar
private static String getFileReaderFunction() {
return "function(path) {\n"
+ " var FileUtils = Java.type('" + FileUtils.class.getCanonicalName() + "');\n"
- + " return FileUtils.readFile(path, " + VAR_CONTEXT + ").value;\n"
+ + " return FileUtils.readFile(path, " + KARATE_DOT_CONTEXT + ").value;\n"
+ "}";
}
@@ -137,7 +137,7 @@ public void buildClient() {
Level.SEVERE,
LoggingFeature.Verbosity.PAYLOAD_TEXT, null));
}
- clientBuilder.register(new RequestFilter(this));
+ clientBuilder.register(new RequestFilter());
if (sslEnabled) {
logger.info("ssl enabled, initializing generic trusted certificate / key-store with algorithm: {}", sslAlgorithm);
SSLContext ssl = SslUtils.getSslContext(sslAlgorithm);
@@ -167,10 +167,7 @@ public Map getVariableBindings() {
Map map = Script.simplify(vars);
if (readFunction != null) {
map.put(VAR_READ, readFunction.getValue());
- }
- // for future function calls if needed, see getFileReaderFunction()
- map.put(VAR_CONTEXT, this);
- map.put(KARATE_NAME, new ScriptBridge(this));
+ }
return map;
}
diff --git a/karate-core/src/main/java/com/intuit/karate/StepDefs.java b/karate-core/src/main/java/com/intuit/karate/StepDefs.java
index 1f5639f43..171710662 100755
--- a/karate-core/src/main/java/com/intuit/karate/StepDefs.java
+++ b/karate-core/src/main/java/com/intuit/karate/StepDefs.java
@@ -200,6 +200,7 @@ public void asssertBoolean(String expression) {
private Invocation.Builder prepare() {
hasUrlBeenSet();
Invocation.Builder builder = target.request();
+ builder.property(ScriptContext.KARATE_DOT_CONTEXT, context);
if (headers != null) {
for (Map.Entry entry : headers.entrySet()) {
builder = builder.header(entry.getKey(), entry.getValue());
@@ -351,7 +352,9 @@ public void soapAction(String action) {
xml = request.getAsString();
}
startTimer();
- response = target.request().header("SOAPAction", action).method("POST", Entity.entity(xml, MediaType.TEXT_XML));
+ Invocation.Builder builder = target.request();
+ builder.property(ScriptContext.KARATE_DOT_CONTEXT, context);
+ response = builder.header("SOAPAction", action).method("POST", Entity.entity(xml, MediaType.TEXT_XML));
stopTimer();
String rawResponse = response.readEntity(String.class);
try {
diff --git a/karate-core/src/test/java/com/intuit/karate/ScriptTest.java b/karate-core/src/test/java/com/intuit/karate/ScriptTest.java
index c3ae78059..b8b436cc0 100755
--- a/karate-core/src/test/java/com/intuit/karate/ScriptTest.java
+++ b/karate-core/src/test/java/com/intuit/karate/ScriptTest.java
@@ -699,6 +699,36 @@ public void testFromJsKarateCallFeatureWithJsonArg() {
assertTrue(Script.matchNamed(MatchType.EQUALS, "res.a", null, "1", ctx).pass);
assertTrue(Script.matchNamed(MatchType.EQUALS, "res.b", null, "2", ctx).pass);
assertTrue(Script.matchNamed(MatchType.EQUALS, "res.c", null, "3", ctx).pass);
+ }
+
+ @Test
+ public void testFromJsKarateGetForNonExistentVariable() {
+ ScriptContext ctx = getContext();
+ Script.assign("fun", "function(){ var foo = karate.get('foo'); return foo ? true : false }", ctx);
+ Script.assign("res", "fun()", ctx);
+ assertTrue(Script.matchNamed(MatchType.EQUALS, "res", null, "false", ctx).pass);
+ }
+
+ @Test
+ public void testFromJsKarateGetForJsonArrayVariable() {
+ ScriptContext ctx = getContext();
+ Script.assign("fun", "function(){ return [1, 2, 3] }", ctx);
+ Script.assign("res", "call fun", ctx);
+ assertTrue(Script.matchNamed(MatchType.EQUALS, "res", null, "[1, 2, 3]", ctx).pass);
}
+
+ @Test
+ public void testFromJsKarateGetForJsonObjectVariableAndCallFeatureAndJs() {
+ ScriptContext ctx = getContext();
+ Script.assign("fun", "read('headers.js')", ctx);
+ Script.assign("res", "call fun", ctx);
+ assertTrue(Script.matchNamed(MatchType.EQUALS, "res", null, "{ foo: 'bar_someValue' }", ctx).pass);
+ Script.assign("signin", "call read('signin.feature')", ctx);
+ Script.assign("ticket", "signin.ticket", ctx);
+ assertTrue(Script.matchNamed(MatchType.EQUALS, "ticket", null, "{ foo: 'bar' }", ctx).pass);
+ Script.assign("res", "call fun", ctx);
+ assertTrue(Script.matchNamed(MatchType.EQUALS, "res", null, "{ foo: 'bar_someValue', baz: 'ban' }", ctx).pass);
+ }
+
}
diff --git a/karate-core/src/test/java/com/intuit/karate/headers.js b/karate-core/src/test/java/com/intuit/karate/headers.js
new file mode 100644
index 000000000..7c5842ab8
--- /dev/null
+++ b/karate-core/src/test/java/com/intuit/karate/headers.js
@@ -0,0 +1,10 @@
+function() {
+ var out = {
+ foo: 'bar_' + someConfig // someConfig is 'start-up' config, will never change
+ };
+ var ticket = karate.get('ticket'); // get can get the 'latest' values of variables
+ if (ticket) {
+ out.baz = 'ban'
+ }
+ return out;
+}
diff --git a/karate-core/src/test/java/com/intuit/karate/signin.feature b/karate-core/src/test/java/com/intuit/karate/signin.feature
new file mode 100644
index 000000000..3155b4537
--- /dev/null
+++ b/karate-core/src/test/java/com/intuit/karate/signin.feature
@@ -0,0 +1,6 @@
+Feature:
+
+Scenario:
+* def iamTicket = { ticket: { foo: 'bar' } }
+* def ticket = iamTicket.ticket
+
diff --git a/karate-demo/README.md b/karate-demo/README.md
index 663b8db78..d8814020f 100644
--- a/karate-demo/README.md
+++ b/karate-demo/README.md
@@ -5,6 +5,7 @@ as well as demonstrate various Karate features and best-practices.
| Example | Demonstrates
----------| --------
[`greeting.feature`](src/test/java/demo/greeting/greeting.feature) | Simple GET requests and multiple scenarios in a test.
+[`headers.feature`](src/test/java/demo/headers/headers.feature) | A simple example of header management using a js file ([`classpath:headers.js`](src/test/java/headers.js)), and also shows how cookies can be accessed and how path and query parameters can be specified.
[`cats.feature`](src/test/java/demo/cats/cats.feature) | Great example of [embedded-expressions](https://github.com/intuit/karate#embedded-expressions) (or JSON / XML templating). Also shows how to set the `Accept` [header](https://github.com/intuit/karate#header) for getting XML from the server.
[`kittens.feature`](src/test/java/demo/cats/kittens.feature) | Reading a complex payload expected response [from a file](https://github.com/intuit/karate#reading-files). You can do the same for request payloads as well. Observe how [JSON templating](https://github.com/intuit/karate#embedded-expressions) makes creating dynamic JSON super-easy, look at [line #24](src/test/java/demo/cats/kittens.feature#L24) for example.
[`upload.feature`](src/test/java/demo/upload/upload.feature) | [Multi-part](https://github.com/intuit/karate#multipart-field) file-upload example, as well as comparing the binary content of a download. Also shows how to assert for expected response [headers](https://github.com/intuit/karate#match-header).
diff --git a/karate-demo/pom.xml b/karate-demo/pom.xml
index 8b9e2fb24..6aabe1900 100755
--- a/karate-demo/pom.xml
+++ b/karate-demo/pom.xml
@@ -5,7 +5,7 @@
com.intuit.karate
karate-parent
- 0.2.5
+ 0.2.6
karate-demo
diff --git a/karate-demo/src/main/java/com/intuit/karate/demo/controller/HeadersController.java b/karate-demo/src/main/java/com/intuit/karate/demo/controller/HeadersController.java
new file mode 100644
index 000000000..fb7cabc6d
--- /dev/null
+++ b/karate-demo/src/main/java/com/intuit/karate/demo/controller/HeadersController.java
@@ -0,0 +1,71 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2017 Intuit Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.intuit.karate.demo.controller;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.CookieValue;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ *
+ * @author pthomas3
+ */
+@RestController
+@RequestMapping("/headers")
+public class HeadersController {
+
+ private Map tokens = new HashMap<>();
+
+ @GetMapping
+ public ResponseEntity getToken(HttpServletResponse response) {
+ String token = UUID.randomUUID().toString();
+ String time = System.currentTimeMillis() + "";
+ tokens.put(token, time);
+ response.addCookie(new Cookie("time", time));
+ return ResponseEntity.ok().body(token);
+ }
+
+ @GetMapping("/{token:.+}")
+ public ResponseEntity validateToken(@CookieValue("time") String time,
+ @RequestHeader("Authorization") String authorization, @PathVariable String token,
+ @RequestParam String url) {
+ String temp = tokens.get(token);
+ if (temp.equals(time) && authorization.equals(token + time + url)) {
+ return ResponseEntity.ok().build();
+ } else {
+ return ResponseEntity.badRequest().build();
+ }
+ }
+
+}
diff --git a/karate-demo/src/test/java/demo/headers/HeadersRunner.java b/karate-demo/src/test/java/demo/headers/HeadersRunner.java
new file mode 100644
index 000000000..6d5c283f3
--- /dev/null
+++ b/karate-demo/src/test/java/demo/headers/HeadersRunner.java
@@ -0,0 +1,11 @@
+package demo.headers;
+
+import demo.TestBase;
+
+/**
+ *
+ * @author pthomas3
+ */
+public class HeadersRunner extends TestBase {
+
+}
diff --git a/karate-demo/src/test/java/demo/headers/headers.feature b/karate-demo/src/test/java/demo/headers/headers.feature
new file mode 100644
index 000000000..c56176390
--- /dev/null
+++ b/karate-demo/src/test/java/demo/headers/headers.feature
@@ -0,0 +1,22 @@
+Feature: greeting end-point
+
+Background:
+* configure headers = read('classpath:headers.js')
+* url demoBaseUrl
+
+Scenario: get auth token and cookie
+
+ Given path 'headers'
+ When method get
+ Then status 200
+ # the next two variables are used by 'headers.js'
+ And def token = response
+ And def time = cookies['time']
+
+ # there is a cookie sent automatically
+ # and an 'Authorization' header set by 'headers.js'
+ Given path 'headers', token
+ And param url = demoBaseUrl
+ When method get
+ Then status 200
+
diff --git a/karate-demo/src/test/java/headers.js b/karate-demo/src/test/java/headers.js
new file mode 100644
index 000000000..cb4ef4f51
--- /dev/null
+++ b/karate-demo/src/test/java/headers.js
@@ -0,0 +1,10 @@
+function() {
+ var token = karate.get('token');
+ var time = karate.get('time');
+ if (token && time) {
+ // demoBaseUrl is like a constant, since it was injected at the time of configuration / init
+ return { Authorization: token + time + demoBaseUrl };
+ } else {
+ return {};
+ }
+}
diff --git a/karate-junit4/pom.xml b/karate-junit4/pom.xml
index 6a7eb23ec..24d8c1258 100755
--- a/karate-junit4/pom.xml
+++ b/karate-junit4/pom.xml
@@ -5,7 +5,7 @@
com.intuit.karate
karate-parent
- 0.2.5
+ 0.2.6
karate-junit4
jar
diff --git a/karate-testng/pom.xml b/karate-testng/pom.xml
index b24d83dd6..d186478f4 100755
--- a/karate-testng/pom.xml
+++ b/karate-testng/pom.xml
@@ -5,7 +5,7 @@
com.intuit.karate
karate-parent
- 0.2.5
+ 0.2.6
karate-testng
jar
diff --git a/pom.xml b/pom.xml
index dc9fc4857..5e1ecf481 100755
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.intuit.karate
karate-parent
- 0.2.5
+ 0.2.6
pom
${project.artifactId}