com.intuit.karate.StepDefs.java Source code

Java tutorial

Introduction

Here is the source code for com.intuit.karate.StepDefs.java

Source

/*
 * 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.ScriptValue.Type;
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.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
import org.bson.BsonDocument;
import org.glassfish.jersey.media.multipart.BodyPart;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.MultiPart;
import org.glassfish.jersey.media.multipart.file.StreamDataBodyPart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

public class StepDefs {

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

    public StepDefs() { // zero-arg constructor for IDE support
        this(ScriptEnv.init(getFeatureDir(), Thread.currentThread().getContextClassLoader()), null, null);
    }

    private static File getFeatureDir() {
        String cwd = new File("").getAbsoluteFile().getPath();
        // TODO non-mac (confirm), non oracle jvm-s
        String javaCommand = System.getProperty("sun.java.command");
        String featurePath = FileUtils.getFeaturePath(javaCommand, cwd);
        if (featurePath == null) {
            File file = new File("");
            logger.warn("unable to derive feature file path, using: {}", file.getAbsolutePath());
            return file;
        } else {
            File file = new File(featurePath);
            logger.info("ide running: {}", file);
            return file.getParentFile();
        }
    }

    public StepDefs(ScriptEnv env, ScriptContext parentContext, Map<String, Object> callArg) {
        context = new ScriptContext(env, parentContext, callArg);
    }

    private String url;
    private WebTarget target;
    private Response response;
    private long startTime;

    private Map<String, Object> headers;
    private ScriptValue request;
    private MultiPart multiPart;
    private MultivaluedMap<String, Object> formFields;

    private boolean useBson;

    private final ScriptContext context;

    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();
        this.url = temp;
        target = context.client.target(temp);
    }

    private void hasUrlBeenSet() {
        if (target == null) {
            throw new RuntimeException("url not set, please refer to the syntax for 'url'");
        }
    }

    @When("^path (.+)")
    public void path(List<String> paths) {
        hasUrlBeenSet();
        for (String path : paths) {
            String temp = Script.eval(path, context).getAsString();
            target = target.path(temp);
        }
    }

    @When("^param ([^\\s]+) = (.+)")
    public void param(String name, String value) {
        hasUrlBeenSet();
        String temp = Script.eval(value, context).getAsString();
        target = target.queryParam(name, temp);
    }

    private Map<String, String> getCookies() {
        Map<String, String> cookies = context.vars.get(ScriptValueMap.VAR_COOKIES, Map.class);
        if (cookies == null) {
            cookies = new HashMap<>();
            context.vars.put(ScriptValueMap.VAR_COOKIES, cookies);
        }
        return cookies;
    }

    @When("^cookie ([^\\s]+) = (.+)")
    public void cookie(String name, String value) {
        Map<String, String> cookies = getCookies();
        String temp = Script.eval(value, context).getAsString();
        cookies.put(name, temp);
    }

    private Map<String, Object> getHeaders() {
        if (headers == null) {
            headers = new HashMap<>();
        }
        return headers;
    }

    @When("^header ([^\\s]+) = (.+)")
    public void header(String name, String value) {
        Map<String, Object> headers = getHeaders();
        String temp = Script.eval(value, context).getAsString();
        headers.put(name, temp);
    }

    private MultivaluedMap<String, Object> getFormFields() {
        if (formFields == null) {
            formFields = new MultivaluedHashMap<>();
        }
        return formFields;
    }

    @When("^form field ([^\\s]+) = (.+)")
    public void formField(String name, String value) {
        MultivaluedMap<String, Object> formFields = getFormFields();
        String temp = Script.eval(value, context).getAsString();
        formFields.add(name, temp);
    }

    @When("^request$")
    public void requestDocString(String requestBody) {
        request(requestBody);
    }

    @When("^request (.+)")
    public void request(String requestBody) {
        request = Script.eval(requestBody, context);
        logger.trace("request value is: {}", request);
    }

    @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);
    }

    private Invocation.Builder prepare() {
        hasUrlBeenSet();
        Invocation.Builder builder = target.request();
        builder.property(ScriptContext.KARATE_DOT_CONTEXT, context);
        if (headers != null) {
            for (Map.Entry<String, Object> entry : headers.entrySet()) {
                builder = builder.header(entry.getKey(), entry.getValue());
            }
        }
        Map<String, String> cookies = context.vars.get(ScriptValueMap.VAR_COOKIES, Map.class);
        if (cookies != null) {
            for (Map.Entry<String, String> entry : cookies.entrySet()) {
                builder = builder.cookie(entry.getKey(), entry.getValue());
            }
        }
        return builder;
    }

    private String getUserSpecifiedContentType() {
        if (headers != null) {
            String type = (String) headers.get("Content-Type");
            if (type != null) {
                return type;
            }
        }
        return null;
    }

    private void makeHttpRequest(Invocation.Builder builder, String method, Entity entity) {
        startTime = System.currentTimeMillis();
        try {
            if (entity != null) {
                response = builder.method(method, entity);
            } else {
                response = builder.method(method);
            }
        } catch (Exception e) {
            long endTime = System.currentTimeMillis();
            long responseTime = endTime - startTime;
            String message = "http call failed after " + responseTime + " milliseconds for URL: " + target.getUri();
            logger.error(e.getMessage() + ", " + message);
            throw new KarateException(message, e);
        }
        long endTime = System.currentTimeMillis();
        long responseTime = endTime - startTime;
        logger.debug("response time in milliseconds: {}", responseTime);
        context.vars.put(ScriptValueMap.VAR_RESPONSE_TIME, responseTime);
    }

    @When("^method (\\w+)")
    public void method(String method) {
        method = method.toUpperCase();
        if ("POST".equals(method) || "PUT".equals(method) || "PATCH".equals(method)) {
            if (multiPart != null) {
                String mediaType = getUserSpecifiedContentType();
                if (mediaType == null) {
                    mediaType = MediaType.MULTIPART_FORM_DATA;
                }
                makeHttpRequest(prepare(), method, Entity.entity(multiPart, mediaType));
            } else if (formFields != null) {
                makeHttpRequest(prepare(), method,
                        Entity.entity(formFields, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
            } else {
                if (request == null || request.isNull()) {
                    String msg = "request body is requred for a " + method + ", please use the 'request' keyword";
                    logger.error(msg);
                    throw new RuntimeException(msg);
                }
                String mediaType = getUserSpecifiedContentType();
                Entity entity;
                switch (request.getType()) {
                case JSON:
                    DocumentContext doc = request.getValue(DocumentContext.class);
                    entity = Entity.json(doc.jsonString());
                    break;
                case MAP:
                    Map<String, Object> map = request.getValue(Map.class);
                    doc = JsonPath.parse(map);
                    entity = Entity.json(doc.jsonString());
                    break;
                case XML:
                    Node node = request.getValue(Node.class);
                    entity = Entity.xml(XmlUtils.toString(node));
                    break;
                case INPUT_STREAM:
                    InputStream is = request.getValue(InputStream.class);
                    if (mediaType == null) {
                        mediaType = MediaType.APPLICATION_OCTET_STREAM;
                    }
                    entity = Entity.entity(is, mediaType);
                    break;
                case BSON_DOCUMENT:
                    BsonDocument bson = request.getValue(BsonDocument.class);
                    ByteArrayInputStream byteStream = new ByteArrayInputStream(BsonUtils.toByteArray(bson));
                    if (mediaType == null) {
                        mediaType = MediaType.APPLICATION_OCTET_STREAM;
                    }
                    entity = Entity.entity(byteStream, mediaType);
                    useBson = true;
                    break;
                default:
                    if (mediaType == null) {
                        mediaType = MediaType.TEXT_PLAIN;
                    }
                    entity = Entity.entity(request.getAsString(), mediaType);
                }
                makeHttpRequest(prepare(), method, entity);
            }
        } else {
            makeHttpRequest(prepare(), method, null);
        }
        unprepare();
    }

    private void unprepare() {
        context.vars.put(ScriptValueMap.VAR_RESPONSE_STATUS, response.getStatus());
        for (Map.Entry<String, NewCookie> entry : response.getCookies().entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue().getValue();
            getCookies().put(key, value);
            logger.trace("set cookie: {} - {}", key, entry.getValue());
        }
        DocumentContext headers = JsonPath.parse(response.getHeaders());
        logger.trace("set response headers: {}", headers.jsonString());
        context.vars.put(ScriptValueMap.VAR_RESPONSE_HEADERS, headers);
        BsonDocument bson = null;
        if (useBson && headers.jsonString().contains(MediaType.APPLICATION_OCTET_STREAM)) {
            byte[] bytes = response.readEntity(byte[].class);
            bson = BsonUtils.fromByteArray(bytes);
        }
        if (bson != null) {
            context.vars.put(ScriptValueMap.VAR_RESPONSE, BsonUtils.bsonToJson(bson));
        } else {
            String rawResponse = response.readEntity(String.class);
            if (Script.isJson(rawResponse)) {
                context.vars.put(ScriptValueMap.VAR_RESPONSE, JsonUtils.toJsonDoc(rawResponse));
            } else if (Script.isXml(rawResponse)) {
                try {
                    context.vars.put(ScriptValueMap.VAR_RESPONSE, XmlUtils.toXmlDoc(rawResponse));
                } catch (Exception e) {
                    logger.warn("xml parsing failed, response data type set to string: {}", e.getMessage());
                    context.vars.put(ScriptValueMap.VAR_RESPONSE, rawResponse);
                }
            } else {
                context.vars.put(ScriptValueMap.VAR_RESPONSE, rawResponse);
            }
        }
        // reset url and some state
        target = context.client.target(url);
        formFields = null;
        multiPart = null;
        request = null;
        useBson = false;
    }

    @When("^soap action( .+)?")
    public void soapAction(String action) {
        hasUrlBeenSet();
        action = Script.eval(action, context).getAsString();
        if (action == null) {
            action = "";
        }
        logger.trace("soap action: '{}'", action);
        if (request == null || request.isNull()) {
            String msg = "request body is requred for a SOAP request, please use the 'request' keyword";
            logger.error(msg);
            throw new RuntimeException(msg);
        }
        String xml;
        switch (request.getType()) {
        case XML:
            Document doc = request.getValue(Document.class);
            xml = XmlUtils.toString(doc);
            break;
        default:
            xml = request.getAsString();
        }
        Invocation.Builder builder = target.request();
        builder.property(ScriptContext.KARATE_DOT_CONTEXT, context);
        builder.header("SOAPAction", action);
        makeHttpRequest(builder, "POST", Entity.entity(xml, MediaType.TEXT_XML));
        String rawResponse = response.readEntity(String.class);
        try {
            context.vars.put(ScriptValueMap.VAR_RESPONSE, XmlUtils.toXmlDoc(rawResponse));
        } catch (Exception e) {
            logger.warn("xml parsing failed, response data type set to string: {}", e.getMessage());
            context.vars.put(ScriptValueMap.VAR_RESPONSE, rawResponse);
        }
        request = null;
    }

    private MultiPart getMultiPart() {
        if (multiPart == null) {
            multiPart = new MultiPart();
        }
        return multiPart;
    }

    @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) {
        MultiPart mp = getMultiPart();
        ScriptValue sv = Script.eval(value, context);
        if (sv.isNull()) {
            throw new RuntimeException("multipart field cannot be null: " + name);
        }
        if (name == null) {
            BodyPart bp;
            switch (sv.getType()) {
            case JSON:
                DocumentContext dc = sv.getValue(DocumentContext.class);
                bp = new BodyPart().entity(dc.jsonString()).type(MediaType.APPLICATION_JSON_TYPE);
                break;
            case XML:
                Document doc = sv.getValue(Document.class);
                bp = new BodyPart().entity(XmlUtils.toString(doc)).type(MediaType.APPLICATION_XML_TYPE);
                break;
            default:
                bp = new BodyPart().entity(sv.getValue());
            }
            mp.bodyPart(bp);
        } else if (sv.getType() == Type.INPUT_STREAM) {
            InputStream is = (InputStream) sv.getValue();
            StreamDataBodyPart part = new StreamDataBodyPart(name, is);
            mp.bodyPart(part);
        } else {
            mp.bodyPart(new FormDataBodyPart(name, sv.getAsString()));
        }
    }

    @Then("^print (.+)")
    public void print(String exp) {
        String temp = Script.eval(exp, context).getAsString();
        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: " + target.getUri().toString() + ", response: "
                    + rawResponse;
            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.callAndUpdateVars(name, arg, context);
    }

    private void handleFailure(AssertionResult ar) {
        if (!ar.pass) {
            logger.error("{}", ar);
            throw new KarateException(ar.message);
        }
    }

}