/* * 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; import com.intuit.karate.http.Cookie; import com.intuit.karate.http.HttpRequest; import com.intuit.karate.http.HttpResponse; import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; import cucumber.api.DataTable; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; import java.io.ByteArrayInputStream; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class StepDefs { private static final Logger LOGGER = LoggerFactory.getLogger(StepDefs.class); public StepDefs() { // zero-arg constructor for IDE support this(getFeatureEnv(), null, null); } private static ScriptEnv getFeatureEnv() { String cwd = new File("").getAbsoluteFile().getPath(); String javaCommand = System.getProperty("sun.java.command"); String featurePath = FileUtils.getFeaturePath(javaCommand, cwd); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (featurePath == null) { File file = new File(""); LOGGER.warn("unable to derive feature file path, using: {}", file.getAbsolutePath()); return ScriptEnv.init(file, null, classLoader); } else { File file = new File(featurePath); LOGGER.info("ide running: {}", file); return ScriptEnv.init(file.getParentFile(), file.getName(), classLoader); } } public StepDefs(ScriptEnv env, ScriptContext parentContext, Map<String, Object> callArg) { context = new ScriptContext(env, parentContext, callArg); request = new HttpRequest(); } private final ScriptContext context; private HttpRequest request; private HttpResponse response; public ScriptContext getContext() { return context; } @When("^configure ([^\\s]+) =$") public void configureDocString(String key, String exp) { configure(key, exp); } @When("^configure ([^\\s]+) = (.+)") public void configure(String key, String exp) { context.configure(key, exp); } @When("^url (.+)") public void url(String expression) { String temp = Script.eval(expression, context).getAsString(); request.setUrl(temp); } @When("^path (.+)") public void path(List<String> paths) { for (String path : paths) { ScriptValue temp = Script.eval(path, context); if (temp.isListLike()) { List list = temp.getAsList(); for (Object o : list) { if (o == null) { continue; } request.addPath(o.toString()); } } else { request.addPath(temp.getAsString()); } } } private List<String> evalList(List<String> values) { List<String> list = new ArrayList(values.size()); try { for (String value : values) { ScriptValue temp = Script.eval(value, context); list.add(temp.getAsString()); } } catch (Exception e) { // hack. for e.g. json with commas would land here String joined = StringUtils.join(values, ", "); ScriptValue temp = Script.eval(joined, context); if (temp.isListLike()) { return temp.getAsList(); } else { return Collections.singletonList(temp.getAsString()); } } return list; } @When("^param ([^\\s]+) = (.+)") public void param(String name, List<String> values) { List<String> list = evalList(values); request.setParam(name, list); } public Map<String, Object> evalMapExpr(String expr) { ScriptValue value = Script.eval(expr, context); if (!value.isMapLike()) { throw new KarateException("cannot convert to map: " + expr); } return value.getAsMap(); } @When("^params (.+)") public void params(String expr) { Map<String, Object> map = evalMapExpr(expr); for (Map.Entry<String, Object> entry : map.entrySet()) { String key = entry.getKey(); Object temp = entry.getValue(); if (temp == null) { request.removeParam(key); } else { if (temp instanceof List) { request.setParam(key, (List) temp); } else { request.setParam(key, temp.toString()); } } } } @When("^cookie ([^\\s]+) = (.+)") public void cookie(String name, String value) { String temp = Script.eval(value, context).getAsString(); request.setCookie(new Cookie(name, temp)); } @When("^cookies (.+)") public void cookies(String expr) { Map<String, Object> map = evalMapExpr(expr); for (Map.Entry<String, Object> entry : map.entrySet()) { String key = entry.getKey(); Object temp = entry.getValue(); if (temp == null) { request.removeCookie(key); } else { request.setCookie(new Cookie(key, temp.toString())); } } } @When("^header ([^\\s]+) = (.+)") public void header(String name, List<String> values) { List<String> list = evalList(values); request.setHeader(name, list); } @When("^headers (.+)") public void headers(String expr) { Map<String, Object> map = evalMapExpr(expr); for (Map.Entry<String, Object> entry : map.entrySet()) { String key = entry.getKey(); Object temp = entry.getValue(); if (temp == null) { request.removeHeader(key); } else { if (temp instanceof List) { request.setHeader(key, (List) temp); } else { request.setHeader(key, temp.toString()); } } } } @When("^form field ([^\\s]+) = (.+)") public void formField(String name, List<String> values) { List<String> list = evalList(values); request.setFormField(name, list); } @When("^form fields (.+)") public void formFields(String expr) { Map<String, Object> map = evalMapExpr(expr); for (Map.Entry<String, Object> entry : map.entrySet()) { String key = entry.getKey(); Object temp = entry.getValue(); if (temp == null) { request.removeFormField(key); } else { if (temp instanceof List) { request.setFormField(key, (List) temp); } else { request.setFormField(key, temp.toString()); } } } } @When("^request$") public void requestDocString(String requestBody) { request(requestBody); } @When("^request (.+)") public void request(String requestBody) { ScriptValue temp = Script.eval(requestBody, context); request.setBody(temp); } @When("^def (.+) =$") public void defDocString(String name, String expression) { def(name, expression); } @When("^def (.+) = (.+)") public void def(String name, String expression) { Script.assign(name, expression, context); } private static DocumentContext toJson(DataTable table) { return JsonPath.parse(table.asMaps(String.class, Object.class)); } @When("^table (.+) =$") public void table(String name, DataTable table) { DocumentContext doc = toJson(table); name = StringUtils.trim(name); context.vars.put(name, doc); } @When("^text (.+) =$") public void textDocString(String name, String expression) { Script.assignText(name, expression, context); } @When("^yaml (.+) =$") public void yamlDocString(String name, String expression) { Script.assignYaml(name, expression, context); } @When("^assert (.+)") public void asssertBoolean(String expression) { AssertionResult ar = Script.assertBoolean(expression, context); handleFailure(ar); } @When("^method (\\w+)") public void method(String method) { request.setMethod(method); response = context.client.invoke(request, context); context.vars.put(ScriptValueMap.VAR_RESPONSE_STATUS, response.getStatus()); context.vars.put(ScriptValueMap.VAR_RESPONSE_TIME, response.getTime()); context.vars.put(ScriptValueMap.VAR_COOKIES, response.getCookies()); if (response.getHeaders() != null) { DocumentContext headers = JsonPath.parse(response.getHeaders()); context.vars.put(ScriptValueMap.VAR_RESPONSE_HEADERS, headers); } else { context.vars.put(ScriptValueMap.VAR_RESPONSE_HEADERS, ScriptValue.NULL); } Object responseBody = convertResponseBody(response.getBody()); if (responseBody instanceof String) { String responseString = (String) responseBody; if (Script.isJson(responseString)) { responseBody = JsonUtils.toJsonDoc(responseString); } else if (Script.isXml(responseString)) { try { responseBody = XmlUtils.toXmlDoc(responseString); } catch (Exception e) { context.logger.warn("xml parsing failed, response data type set to string: {}", e.getMessage()); } } } context.vars.put(ScriptValueMap.VAR_RESPONSE, responseBody); String prevUrl = request.getUrl(); Map<String, Cookie> prevCookies = request.getCookies(); request = new HttpRequest(); request.setUrl(prevUrl); if (prevCookies == null) { request.setCookies(response.getCookies()); } else { if (response.getCookies() != null) { prevCookies.putAll(response.getCookies()); } request.setCookies(prevCookies); } } private Object convertResponseBody(byte[] bytes) { if (bytes == null) { return null; } // if a byte array contains a negative-signed byte, // then the string conversion will corrupt it. // in that case just return the byte array stream try { String rawString = new String(bytes, "utf-8"); if (Arrays.equals(bytes, rawString.getBytes())) { return rawString; } } catch (Exception e) { context.logger.warn("response bytes to string conversion failed: {}", e.getMessage()); } return new ByteArrayInputStream(bytes); } @When("^soap action( .+)?") public void soapAction(String action) { action = Script.eval(action, context).getAsString(); if (action == null) { action = ""; } request.setHeader("SOAPAction", action); request.setHeader("Content-Type", "text/xml"); method("post"); } @When("^multipart entity (.+)") public void multiPartEntity(String value) { multiPart(null, value); } @When("^multipart field (.+) = (.+)") public void multiPartFormField(String name, String value) { multiPart(name, value); } public void multiPart(String name, String value) { ScriptValue sv = Script.eval(value, context); request.addMultiPartItem(name, sv); } @Then("^print (.+)") public void print(String exp) { String temp = Script.evalInNashorn(exp, context).getAsString(); context.logger.info("[print] {}", temp); } @Then("^status (\\d+)") public void status(int status) { if (status != response.getStatus()) { String rawResponse = context.vars.get(ScriptValueMap.VAR_RESPONSE).getAsString(); String responseTime = context.vars.get(ScriptValueMap.VAR_RESPONSE_TIME).getAsString(); String message = "status code was: " + response.getStatus() + ", expected: " + status + ", response time: " + responseTime + ", url: " + response.getUri() + ", response: " + rawResponse; context.logger.error(message); throw new KarateException(message); } } private static MatchType toMatchType(String each, String only, boolean contains) { if (each == null) { if (contains) { return only == null ? MatchType.CONTAINS : MatchType.CONTAINS_ONLY; } else { return MatchType.EQUALS; } } else { if (contains) { return MatchType.EACH_CONTAINS; } else { return MatchType.EACH_EQUALS; } } } @Then("^match (each )?([^\\s]+)( [^\\s]+)? ==$") public void matchEqualsDocString(String each, String name, String path, String expected) { matchEquals(each, name, path, expected); } @Then("^match (each )?([^\\s]+)( [^\\s]+)? contains( only)?$") public void matchContainsDocString(String each, String name, String path, String only, String expected) { matchContains(each, name, path, only, expected); } @Then("^match (each )?([^\\s]+)( [^\\s]+)? == (.+)") public void matchEquals(String each, String name, String path, String expected) { MatchType mt = toMatchType(each, null, false); matchNamed(mt, name, path, expected); } @Then("^match (each )?([^\\s]+)( [^\\s]+)? contains( only)?(.+)") public void matchContains(String each, String name, String path, String only, String expected) { MatchType mt = toMatchType(each, only, true); matchNamed(mt, name, path, expected); } public void matchNamed(MatchType matchType, String name, String path, String expected) { AssertionResult ar = Script.matchNamed(matchType, name, path, expected, context); handleFailure(ar); } @Then("^set ([^\\s]+)( .+)? =$") public void setByPathDocString(String name, String path, String value) { setNamedByPath(name, path, value); } @Then("^set ([^\\s]+)( .+)? = (.+)") public void setByPath(String name, String path, String value) { setNamedByPath(name, path, value); } public void setNamedByPath(String name, String path, String value) { Script.setValueByPath(name, path, value, context); } @Given("^call ([^\\s]+)( .*)?") public final void callAndUpdateVars(String name, String arg) { Script.callAndUpdateVarsIfMapReturned(name, arg, context); } private void handleFailure(AssertionResult ar) { if (!ar.pass) { context.logger.error("{}", ar); throw new KarateException(ar.message); } } }