diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml new file mode 100644 index 000000000..033d769e7 --- /dev/null +++ b/.github/workflows/maven-build.yml @@ -0,0 +1,29 @@ +name: maven-build + +on: + push: + branches: [ master, develop ] + pull_request: + branches: [ develop ] + +jobs: + build: + timeout-minutes: 10 + runs-on: ubuntu-latest + steps: + - name: git checkout + uses: actions/checkout@v2 + - name: set up jdk 8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: cache maven packages + uses: actions/cache@v2 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: build with maven + run: mvn -B clean install -P pre-release -Djavacpp.platform=linux-x86_64 + - name: build and test with docker + run: ./build-docker.sh \ No newline at end of file diff --git a/.gitignore b/.gitignore index 03d05e299..dd2c7980c 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,9 @@ karate-demo/activemq-data/ karate-demo/*.pem karate-demo/*.jks karate-demo/*.der -karate-netty/*.pem -karate-netty/*.jks -karate-netty/*.der +karate-core/*.pem +karate-core/*.jks +karate-core/*.der karate-robot/tessdata karate-junit4/src/test/java/com/intuit/karate/junit4/dev karate-robot/src/test/java/robot/dev diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a838b08b7..000000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: java -cache: - directories: - - "$HOME/.m2" -jdk: - - openjdk8 -script: mvn install -P pre-release -Dmaven.javadoc.skip=true -B -V -Djavacpp.platform=linux-x86_64 diff --git a/README.md b/README.md index af127c161..6441d33fb 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Karate ## Test Automation Made `Simple.` -[![Maven Central](https://img.shields.io/maven-central/v/com.intuit.karate/karate-core.svg)](https://mvnrepository.com/artifact/com.intuit.karate/karate-core) [![Build Status](https://travis-ci.org/intuit/karate.svg?branch=master)](https://travis-ci.org/intuit/karate) [![GitHub release](https://img.shields.io/github/release/intuit/karate.svg)](https://github.com/intuit/karate/releases) [![Support Slack](https://img.shields.io/badge/support-slack-red.svg)](https://github.com/intuit/karate/wiki/Support) [![Twitter Follow](https://img.shields.io/twitter/follow/KarateDSL.svg?style=social&label=Follow)](https://twitter.com/KarateDSL) +[![Maven Central](https://img.shields.io/maven-central/v/com.intuit.karate/karate-core.svg)](https://search.maven.org/artifact/com.intuit.karate/karate-core) [ ![build](https://github.com/intuit/karate/workflows/maven-build/badge.svg)](https://github.com/intuit/karate/actions?query=workflow%3Amaven-build) [![GitHub release](https://img.shields.io/github/release/intuit/karate.svg)](https://github.com/intuit/karate/releases) [![Support Slack](https://img.shields.io/badge/support-slack-red.svg)](https://github.com/intuit/karate/wiki/Support) [![Twitter Follow](https://img.shields.io/twitter/follow/KarateDSL.svg?style=social&label=Follow)](https://twitter.com/KarateDSL) Karate is the only open-source tool to combine API test-automation, [mocks](karate-netty), [performance-testing](karate-gatling) and even [UI automation](karate-core) into a **single**, *unified* framework. The BDD syntax popularized by Cucumber is language-neutral, and easy for even non-programmers. Powerful JSON & XML assertions are built-in, and you can run tests in parallel for speed. diff --git a/build-docker.sh b/build-docker.sh new file mode 100755 index 000000000..828961852 --- /dev/null +++ b/build-docker.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -x -e + +# assume that karate jars are installed in maven local repo +# mvn clean install -P pre-release -DskipTests + +# copy only karate jars to a place where the docker image build can add from +KARATE_REPO=karate-docker/karate-chrome/target/repository/com/intuit +mkdir -p ${KARATE_REPO} +cp -r ~/.m2/repository/com/intuit/karate ${KARATE_REPO} + +# create / copy the karate fatjar so that the docker image build can add it +mvn -f karate-core/pom.xml package -P fatjar -DskipTests +KARATE_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) +cp karate-core/target/karate-${KARATE_VERSION}.jar karate-docker/karate-chrome/target/karate.jar + +# build karate-chrome docker image that includes karate fatjar + maven jars for convenience +docker build -t karate-chrome karate-docker/karate-chrome + +# just in case a previous run had hung (likely only in local dev) +docker stop karate || true + +# note that this command is run as a background process +docker run --name karate --rm --cap-add=SYS_ADMIN -v "$PWD":/karate -v "$HOME"/.m2:/root/.m2 karate-chrome & + +# just ensure that the docker container named "karate" exists after the above command +# it does not have to have completed startup, the command / karate test below will wait +sleep 5 + +# run tests against chrome +docker exec -w /karate karate mvn test -f karate-e2e-tests/pom.xml -Dtest=driver.DockerRunner +docker stop karate +wait diff --git a/examples/gatling/pom.xml b/examples/gatling/pom.xml index ce8ebdfe0..17d7718da 100755 --- a/examples/gatling/pom.xml +++ b/examples/gatling/pom.xml @@ -12,15 +12,10 @@ 1.8 3.6.0 2.0.0 - 3.0.2 + 3.1.0 - - com.intuit.karate - karate-apache - ${karate.version} - com.intuit.karate karate-gatling diff --git a/examples/gatling/src/test/java/mock/MockUtils.java b/examples/gatling/src/test/java/mock/MockUtils.java index 560642753..d363ea615 100755 --- a/examples/gatling/src/test/java/mock/MockUtils.java +++ b/examples/gatling/src/test/java/mock/MockUtils.java @@ -1,11 +1,9 @@ package mock; -import com.intuit.karate.FileUtils; +import com.intuit.karate.core.MockServer; import com.intuit.karate.PerfContext; import com.intuit.karate.Runner; -import com.intuit.karate.netty.FeatureServer; -import java.io.File; import java.util.Collections; import java.util.List; import java.util.Map; @@ -18,8 +16,7 @@ public class MockUtils { public static void startServer() { - File file = FileUtils.getFileRelativeTo(MockUtils.class, "mock.feature"); - FeatureServer server = FeatureServer.start(file, 0, false, null); + MockServer server = MockServer.feature("classpath:mock/mock.feature").build(); System.setProperty("mock.cats.url", "http://localhost:" + server.getPort() + "/cats"); } diff --git a/examples/jobserver/pom.xml b/examples/jobserver/pom.xml index c6688de1b..6a4e6d4d3 100644 --- a/examples/jobserver/pom.xml +++ b/examples/jobserver/pom.xml @@ -10,17 +10,11 @@ UTF-8 1.8 - 3.6.0 - 0.9.6 + 3.8.1 + 2.0.0 - - - com.intuit.karate - karate-apache - ${karate.version} - test - + com.intuit.karate karate-junit5 @@ -30,7 +24,7 @@ net.masterthought cucumber-reporting - 3.8.0 + 5.3.1 test diff --git a/examples/jobserver/src/test/java/jobtest/simple/SimpleDockerJobRunner.java b/examples/jobserver/src/test/java/jobtest/simple/SimpleDockerJobRunner.java index 92cbdf865..604aa3d40 100644 --- a/examples/jobserver/src/test/java/jobtest/simple/SimpleDockerJobRunner.java +++ b/examples/jobserver/src/test/java/jobtest/simple/SimpleDockerJobRunner.java @@ -15,7 +15,7 @@ public class SimpleDockerJobRunner { @Test void testJobManager() { MavenJobConfig config = new MavenJobConfig(2, "host.docker.internal", 0); - Results results = Runner.path("classpath:jobtest/simple").startServerAndWait(config); + Results results = Runner.path("classpath:jobtest/simple").parallel(1); //.startServerAndWait(config); ReportUtils.generateReport(results.getReportDir()); } diff --git a/examples/jobserver/src/test/java/jobtest/simple/SimpleLocalJobRunner.java b/examples/jobserver/src/test/java/jobtest/simple/SimpleLocalJobRunner.java index 978a2e58c..d8db6d78b 100644 --- a/examples/jobserver/src/test/java/jobtest/simple/SimpleLocalJobRunner.java +++ b/examples/jobserver/src/test/java/jobtest/simple/SimpleLocalJobRunner.java @@ -21,7 +21,7 @@ public class SimpleLocalJobRunner { @Test void testJobManager() { - MavenJobConfig config = new MavenJobConfig(-1, "127.0.0.1", 0) { + MavenJobConfig config = new MavenJobConfig(2, "127.0.0.1", 0) { @Override public void startExecutors(String uniqueId, String serverUrl) throws Exception { int executorCount = 2; @@ -35,7 +35,7 @@ public void startExecutors(String uniqueId, String serverUrl) throws Exception { }; // export KARATE_TEST="foo" config.addEnvPropKey("KARATE_TEST"); - Results results = Runner.path("classpath:jobtest/simple").startServerAndWait(config); + Results results = Runner.path("classpath:jobtest/simple").jobManager(config); ReportUtils.generateReport(results.getReportDir()); } diff --git a/examples/jobserver/src/test/java/jobtest/simple/SimpleRunner.java b/examples/jobserver/src/test/java/jobtest/simple/SimpleRunner.java index 5d2a2efba..5f0d669a8 100644 --- a/examples/jobserver/src/test/java/jobtest/simple/SimpleRunner.java +++ b/examples/jobserver/src/test/java/jobtest/simple/SimpleRunner.java @@ -14,7 +14,8 @@ public class SimpleRunner { @Test void test() { - Results results = Runner.path("classpath:jobtest/simple").tags("~@ignore").parallel(1); + Results results = Runner.path("classpath:jobtest/simple") + .outputCucumberJson(true).tags("~@ignore").parallel(1); ReportUtils.generateReport(results.getReportDir()); assertEquals(0, results.getFailCount(), results.getErrorMessages()); } diff --git a/examples/jobserver/src/test/java/jobtest/simple/simple1.feature b/examples/jobserver/src/test/java/jobtest/simple/simple1.feature index 1d0ae38c2..88dbac3c6 100644 --- a/examples/jobserver/src/test/java/jobtest/simple/simple1.feature +++ b/examples/jobserver/src/test/java/jobtest/simple/simple1.feature @@ -1,15 +1,7 @@ Feature: simple 1 Background: -* configure responseDelay = 1000 +* print 'in background 1' Scenario: 1-one * print '1-one' -* def karateTest = java.lang.System.getenv('KARATE_TEST') -* print '*** KARATE_TEST: ', karateTest - -Scenario: 1-two -* print '1-two' - -Scenario: 1-three -* print '1-three' diff --git a/examples/jobserver/src/test/java/jobtest/simple/simple3.feature b/examples/jobserver/src/test/java/jobtest/simple/simple3.feature index 19a4f4aa5..b63321203 100644 --- a/examples/jobserver/src/test/java/jobtest/simple/simple3.feature +++ b/examples/jobserver/src/test/java/jobtest/simple/simple3.feature @@ -5,8 +5,3 @@ Scenario: 3-one Scenario: 3-two * print '3-two' - -Scenario: 3-three -* print '3-three' -Given url 'http://httpbin.org/get' -When method get diff --git a/examples/jobserver/src/test/java/jobtest/web/WebDockerJobRunner.java b/examples/jobserver/src/test/java/jobtest/web/WebDockerJobRunner.java index 0f8e0b680..859d1bd72 100644 --- a/examples/jobserver/src/test/java/jobtest/web/WebDockerJobRunner.java +++ b/examples/jobserver/src/test/java/jobtest/web/WebDockerJobRunner.java @@ -16,7 +16,7 @@ public class WebDockerJobRunner { void test() { MavenChromeJobConfig config = new MavenChromeJobConfig(2, "host.docker.internal", 0); System.setProperty("karate.env", "jobserver"); - Results results = Runner.path("classpath:jobtest/web").startServerAndWait(config); + Results results = Runner.path("classpath:jobtest/web").parallel(1); ReportUtils.generateReport(results.getReportDir()); } diff --git a/examples/jobserver/src/test/java/logback-test.xml b/examples/jobserver/src/test/java/logback-test.xml index fea195eb0..d95a16ed2 100644 --- a/examples/jobserver/src/test/java/logback-test.xml +++ b/examples/jobserver/src/test/java/logback-test.xml @@ -16,7 +16,7 @@ - + diff --git a/karate-apache/pom.xml b/karate-apache/pom.xml deleted file mode 100755 index fd523dd38..000000000 --- a/karate-apache/pom.xml +++ /dev/null @@ -1,60 +0,0 @@ - - 4.0.0 - - - com.intuit.karate - karate-parent - 2.0.0 - - karate-apache - jar - - - 4.5.12 - - - - - - com.intuit.karate - karate-core - ${project.version} - - - - org.apache.httpcomponents - httpclient - ${apache.httpcomponents.version} - - - commons-logging - commons-logging - - - - - - org.apache.httpcomponents - httpmime - ${apache.httpcomponents.version} - - - - org.slf4j - jcl-over-slf4j - - 1.7.25 - runtime - - - - junit - junit - ${junit.version} - test - - - - - diff --git a/karate-apache/src/main/java/com/intuit/karate/http/apache/ApacheHttpClient.java b/karate-apache/src/main/java/com/intuit/karate/http/apache/ApacheHttpClient.java deleted file mode 100644 index 4fba22772..000000000 --- a/karate-apache/src/main/java/com/intuit/karate/http/apache/ApacheHttpClient.java +++ /dev/null @@ -1,359 +0,0 @@ -/* - * 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.http.apache; - -import com.intuit.karate.Config; -import com.intuit.karate.FileUtils; -import com.intuit.karate.core.ScenarioContext; -import com.intuit.karate.http.*; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHost; -import org.apache.http.ParseException; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CookieStore; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.methods.RequestBuilder; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.config.SocketConfig; -import org.apache.http.conn.ssl.*; -import org.apache.http.cookie.Cookie; -import org.apache.http.impl.client.*; -import org.apache.http.impl.conn.SystemDefaultRoutePlanner; -import org.apache.http.impl.cookie.BasicClientCookie; -import org.apache.http.protocol.BasicHttpContext; -import org.apache.http.ssl.SSLContextBuilder; -import org.apache.http.ssl.SSLContexts; - -import javax.net.ssl.SSLContext; -import java.io.IOException; -import java.io.InputStream; -import java.net.*; -import java.nio.charset.Charset; -import java.security.KeyStore; -import java.time.ZonedDateTime; -import java.time.format.DateTimeParseException; -import java.util.*; -import java.util.Map.Entry; - -import static com.intuit.karate.http.Cookie.*; - -/** - * @author pthomas3 - */ -public class ApacheHttpClient extends HttpClient { - - public static final String URI_CONTEXT_KEY = ApacheHttpClient.class.getName() + ".URI"; - - private HttpClientBuilder clientBuilder; - private URIBuilder uriBuilder; - private RequestBuilder requestBuilder; - private CookieStore cookieStore; - private Charset charset; - - private void build() { - try { - URI uri = uriBuilder.build(); - String method = request.getMethod(); - requestBuilder = RequestBuilder.create(method).setUri(uri); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void configure(Config config, ScenarioContext context) { - clientBuilder = HttpClientBuilder.create(); - charset = config.getCharset(); - if (!config.isFollowRedirects()) { - clientBuilder.disableRedirectHandling(); - } else { // support redirect on POST by default - clientBuilder.setRedirectStrategy(new LaxRedirectStrategy()); - } - clientBuilder.useSystemProperties(); - cookieStore = new BasicCookieStore(); - clientBuilder.setDefaultCookieStore(cookieStore); - clientBuilder.setDefaultCookieSpecRegistry(LenientCookieSpec.registry()); - RequestLoggingInterceptor requestInterceptor = new RequestLoggingInterceptor(context); - clientBuilder.addInterceptorLast(requestInterceptor); - clientBuilder.addInterceptorLast(new ResponseLoggingInterceptor(requestInterceptor, context)); - if (config.isSslEnabled()) { - // System.setProperty("jsse.enableSNIExtension", "false"); - String algorithm = config.getSslAlgorithm(); // could be null - KeyStore trustStore = HttpUtils.getKeyStore(context, - config.getSslTrustStore(), config.getSslTrustStorePassword(), config.getSslTrustStoreType()); - KeyStore keyStore = HttpUtils.getKeyStore(context, - config.getSslKeyStore(), config.getSslKeyStorePassword(), config.getSslKeyStoreType()); - SSLContext sslContext; - try { - SSLContextBuilder builder = SSLContexts.custom() - .setProtocol(algorithm); // will default to TLS if null - if (trustStore == null && config.isSslTrustAll()) { - builder = builder.loadTrustMaterial(new TrustAllStrategy()); - } else { - if (config.isSslTrustAll()) { - builder = builder.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()); - } else { - builder = builder.loadTrustMaterial(trustStore, null); // will use system / java default - } - } - if (keyStore != null) { - char[] keyPassword = config.getSslKeyStorePassword() == null ? null : config.getSslKeyStorePassword().toCharArray(); - builder = builder.loadKeyMaterial(keyStore, keyPassword); - } - sslContext = builder.build(); - SSLConnectionSocketFactory socketFactory; - if (keyStore != null) { - socketFactory = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier()); - } else { - socketFactory = new LenientSslConnectionSocketFactory(sslContext, new NoopHostnameVerifier()); - } - clientBuilder.setSSLSocketFactory(socketFactory); - } catch (Exception e) { - context.logger.error("ssl context init failed: {}", e.getMessage()); - throw new RuntimeException(e); - } - } - RequestConfig.Builder configBuilder = RequestConfig.custom() - .setCookieSpec(LenientCookieSpec.KARATE) - .setConnectTimeout(config.getConnectTimeout()) - .setSocketTimeout(config.getReadTimeout()); - if (config.getLocalAddress() != null) { - try { - InetAddress localAddress = InetAddress.getByName(config.getLocalAddress()); - configBuilder.setLocalAddress(localAddress); - } catch (Exception e) { - context.logger.warn("failed to resolve local address: {} - {}", config.getLocalAddress(), e.getMessage()); - } - } - clientBuilder.setDefaultRequestConfig(configBuilder.build()); - SocketConfig.Builder socketBuilder = SocketConfig.custom().setSoTimeout(config.getConnectTimeout()); - clientBuilder.setDefaultSocketConfig(socketBuilder.build()); - if (config.getProxyUri() != null) { - try { - URI proxyUri = new URIBuilder(config.getProxyUri()).build(); - clientBuilder.setProxy(new HttpHost(proxyUri.getHost(), proxyUri.getPort(), proxyUri.getScheme())); - if (config.getProxyUsername() != null && config.getProxyPassword() != null) { - CredentialsProvider credsProvider = new BasicCredentialsProvider(); - credsProvider.setCredentials( - new AuthScope(proxyUri.getHost(), proxyUri.getPort()), - new UsernamePasswordCredentials(config.getProxyUsername(), config.getProxyPassword())); - clientBuilder.setDefaultCredentialsProvider(credsProvider); - } - if (config.getNonProxyHosts() != null) { - ProxySelector proxySelector = new ProxySelector() { - private final List proxyExceptions = config.getNonProxyHosts(); - - @Override - public List select(URI uri) { - return Collections.singletonList(proxyExceptions.contains(uri.getHost()) - ? Proxy.NO_PROXY - : new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyUri.getHost(), proxyUri.getPort()))); - } - - @Override - public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { - context.logger.info("connect failed to uri: {}", uri, ioe); - } - }; - clientBuilder.setRoutePlanner(new SystemDefaultRoutePlanner(proxySelector)); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - @Override - protected void buildUrl(String url) { - try { - uriBuilder = new URIBuilder(url); - build(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - protected void buildPath(String path) { - String temp = uriBuilder.getPath(); - if (temp == null) { - temp = ""; - } - if (!temp.endsWith("/")) { - temp = temp + "/"; - } - if (path.startsWith("/")) { - path = path.substring(1); - } - uriBuilder.setPath(temp + path); - build(); - } - - @Override - protected void buildParam(String name, Object... values) { - if (values.length == 1) { - Object v = values[0]; - if (v != null) { - uriBuilder.setParameter(name, v.toString()); - } - } else { - Arrays.stream(values) - .filter(Objects::nonNull) - .forEach(o -> uriBuilder.addParameter(name, o.toString())); - } - build(); - } - - @Override - protected void buildHeader(String name, Object value, boolean replace) { - if (replace) { - requestBuilder.removeHeaders(name); - } - requestBuilder.addHeader(name, value == null ? null : value.toString()); - } - - @Override - protected void buildCookie(com.intuit.karate.http.Cookie c) { - BasicClientCookie cookie = new BasicClientCookie(c.getName(), c.getValue()); - for (Entry entry : c.entrySet()) { - switch (entry.getKey()) { - case DOMAIN: - cookie.setDomain(entry.getValue()); - break; - case PATH: - cookie.setPath(entry.getValue()); - break; - case EXPIRES: // add expires field for cookie. - try { - cookie.setExpiryDate(Date.from(ZonedDateTime.parse(entry.getValue(), DTFMTR_RFC1123).toInstant())); - } - catch ( DateTimeParseException ex) - { - System.err.println("ex ->" + ex.getLocalizedMessage()); - } - break; - case MAX_AGE: // set max age - int maxAge = Integer.parseInt(entry.getValue()); - if (maxAge >= 0) { // only for valid maxAge for cookie expiration. - cookie.setExpiryDate(new Date(System.currentTimeMillis() + (maxAge * 1000))); - } - break; - } - } - if (cookie.getDomain() == null) { - cookie.setDomain(uriBuilder.getHost()); - } - cookieStore.addCookie(cookie); - } - - @Override - protected HttpEntity getEntity(List items, String mediaType) { - return ApacheHttpUtils.getEntity(items, mediaType, charset); - } - - @Override - protected HttpEntity getEntity(MultiValuedMap fields, String mediaType) { - return ApacheHttpUtils.getEntity(fields, mediaType, charset); - } - - @Override - protected HttpEntity getEntity(String value, String mediaType) { - return ApacheHttpUtils.getEntity(value, mediaType, charset); - } - - @Override - protected HttpEntity getEntity(InputStream value, String mediaType) { - return ApacheHttpUtils.getEntity(value, mediaType, charset); - } - - @Override - protected HttpResponse makeHttpRequest(HttpEntity entity, ScenarioContext context) { - if (entity != null) { - requestBuilder.setEntity(entity); - requestBuilder.setHeader(entity.getContentType()); - } - HttpUriRequest httpRequest = requestBuilder.build(); - CloseableHttpClient client = clientBuilder.build(); - BasicHttpContext httpContext = new BasicHttpContext(); - httpContext.setAttribute(URI_CONTEXT_KEY, getRequestUri()); - CloseableHttpResponse httpResponse; - byte[] bytes; - try { - httpResponse = client.execute(httpRequest, httpContext); - HttpEntity responseEntity = httpResponse.getEntity(); - if (responseEntity == null || responseEntity.getContent() == null) { - bytes = new byte[0]; - } else { - InputStream is = responseEntity.getContent(); - bytes = FileUtils.toBytes(is); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - HttpRequest actualRequest = context.getPrevRequest(); - HttpResponse response = new HttpResponse(actualRequest.getStartTime(), actualRequest.getEndTime()); - response.setUri(getRequestUri()); - response.setBody(bytes); - response.setStatus(httpResponse.getStatusLine().getStatusCode()); - for (Cookie c : cookieStore.getCookies()) { - com.intuit.karate.http.Cookie cookie = new com.intuit.karate.http.Cookie(c.getName(), c.getValue()); - // while preparing the cookie in buildCookie method we used a BasicClientCookie. This conversion ensures we get path correctly. - BasicClientCookie cc = (BasicClientCookie) c; - cookie.put(DOMAIN, cc.getDomain()); - cookie.put(PATH, cc.getPath()); - if (c.getExpiryDate() != null) { - cookie.put(EXPIRES, c.getExpiryDate().getTime() + ""); - } - cookie.put(PERSISTENT, c.isPersistent() + ""); - cookie.put(SECURE, c.isSecure() + ""); - response.addCookie(cookie); - } - cookieStore.clear(); // we rely on the StepDefs for cookie 'persistence' - for (Header header : httpResponse.getAllHeaders()) { // rely on setting cookies from set-cookie header, else these will be skipped. - if( header.getName().equalsIgnoreCase("Set-Cookie")) - { - List cookieMap = HttpCookie.parse(header.getValue()); - cookieMap.forEach( ck -> { - com.intuit.karate.http.Cookie cookie = new com.intuit.karate.http.Cookie(ck.getName(), ck.getValue()); - cookie.put(DOMAIN, ck.getDomain()); - cookie.put(PATH, null != ck.getPath() ? ck.getPath() : "/"); // lets make sure path is not null. - cookie.put(MAX_AGE, ck.getMaxAge() + ""); - }); - } - response.addHeader(header.getName(), header.getValue()); - } - return response; - } - - @Override - protected String getRequestUri() { - return requestBuilder.getUri().toString(); - } - -} diff --git a/karate-apache/src/main/java/com/intuit/karate/http/apache/ApacheHttpUtils.java b/karate-apache/src/main/java/com/intuit/karate/http/apache/ApacheHttpUtils.java deleted file mode 100644 index b9d274220..000000000 --- a/karate-apache/src/main/java/com/intuit/karate/http/apache/ApacheHttpUtils.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * 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.http.apache; - -import com.intuit.karate.ScriptValue; -import com.intuit.karate.StringUtils; -import com.intuit.karate.http.HttpBody; -import com.intuit.karate.http.HttpUtils; -import com.intuit.karate.http.MultiPartItem; -import com.intuit.karate.http.MultiValuedMap; - -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import io.netty.handler.codec.http.HttpUtil; -import org.apache.http.HttpEntity; -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.InputStreamEntity; -import org.apache.http.entity.StringEntity; -import org.apache.http.entity.mime.FormBodyPartBuilder; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.entity.mime.content.ByteArrayBody; -import org.apache.http.entity.mime.content.ContentBody; -import org.apache.http.entity.mime.content.InputStreamBody; -import org.apache.http.entity.mime.content.StringBody; -import org.apache.http.message.BasicNameValuePair; - -/** - * - * @author pthomas3 - */ -public class ApacheHttpUtils { - - private ApacheHttpUtils() { - // only static methods - } - - public static HttpBody toBody(HttpEntity entity) { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - entity.writeTo(baos); - return HttpBody.bytes(baos.toByteArray(), entity.getContentType().getValue()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - // all this complexity is to be able to support "bad" values such as an empty string - private static ContentType getContentType(String mediaType, Charset charset) { - if (!HttpUtils.isPrintable(mediaType)) { - try { - return ContentType.create(mediaType); - } catch (Exception e) { - return null; - } - } - // if charset is null that means mediaType does not contains any charset - Charset existingCharset = HttpUtil.getCharset(mediaType, null); - // appending charset if not present - if (Objects.isNull(existingCharset)) { - StringBuilder sb = new StringBuilder(mediaType).append("; ").append(HttpUtils.CHARSET); - if (charset != null) { // if user set configure charset = null (to omit that piece of the header) - sb.append("=").append(charset.name()); - } - mediaType = sb.toString(); - } - return ContentType.parse(mediaType); - } - - public static HttpEntity getEntity(InputStream is, String mediaType, Charset charset) { - try { - return new InputStreamEntity(is, is.available(), getContentType(mediaType, charset)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static HttpEntity getEntity(String value, String mediaType, Charset charset) { - try { - ContentType ct = getContentType(mediaType, charset); - if (ct == null) { // "bad" value such as an empty string - StringEntity entity = new StringEntity(value); - entity.setContentType(mediaType); - return entity; - } else { - return new StringEntity(value, ct); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static HttpEntity getEntity(MultiValuedMap fields, String mediaType, Charset charset) { - List list = new ArrayList<>(fields.size()); - for (Map.Entry entry : fields.entrySet()) { - String stringValue; - List values = entry.getValue(); - if (values == null) { - stringValue = null; - } else if (values.size() == 1) { - Object value = values.get(0); - if (value == null) { - stringValue = null; - } else if (value instanceof String) { - stringValue = (String) value; - } else { - stringValue = value.toString(); - } - } else { - stringValue = StringUtils.join(values, ','); - } - list.add(new BasicNameValuePair(entry.getKey(), stringValue)); - } - try { - Charset cs = HttpUtils.parseContentTypeCharset(mediaType); - if (cs == null) { - cs = charset; - } - String raw = URLEncodedUtils.format(list, cs); - int pos = mediaType.indexOf(';'); - if (pos != -1) { // strip out charset param from content-type - mediaType = mediaType.substring(0, pos); - } - return new StringEntity(raw, ContentType.create(mediaType, cs)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static HttpEntity getEntity(List items, String mediaType, Charset charset) { - boolean hasNullName = false; - for (MultiPartItem item : items) { - if (item.getName() == null) { - hasNullName = true; - break; - } - } - if (hasNullName) { // multipart/related - String boundary = HttpUtils.generateMimeBoundaryMarker(); - String text = HttpUtils.multiPartToString(items, boundary); - ContentType ct = ContentType.parse(mediaType).withParameters(new BasicNameValuePair("boundary", boundary)); - return new StringEntity(text, ct); - } else { - MultipartEntityBuilder builder = MultipartEntityBuilder.create().setContentType(ContentType.parse(mediaType)); - for (MultiPartItem item : items) { - if (item.getValue() == null || item.getValue().isNull()) { - continue; - } - String name = item.getName(); - if (name == null) { - // will never happen because we divert this flow to the home-made multi-part builder above - // builder.addPart(bodyPart); - } else { - ScriptValue sv = item.getValue(); - String ct = item.getContentType(); - if (ct == null) { - ct = HttpUtils.getContentType(sv); - } - ContentType contentType = ContentType.create(ct); - if (HttpUtils.isPrintable(ct)) { - Charset cs = HttpUtils.parseContentTypeCharset(mediaType); - if (cs == null) { - cs = charset; - } - contentType = contentType.withCharset(cs); - } - FormBodyPartBuilder formBuilder = FormBodyPartBuilder.create().setName(name); - ContentBody contentBody; - String filename = item.getFilename(); - if (filename != null) { - contentBody = new ByteArrayBody(sv.getAsByteArray(), contentType, filename); - } else if (sv.isStream()) { - contentBody = new InputStreamBody(sv.getAsStream(), contentType); - } else { - contentBody = new StringBody(sv.getAsString(), contentType); - } - formBuilder = formBuilder.setBody(contentBody); - builder = builder.addPart(formBuilder.build()); - } - } - return builder.build(); - } - } - -} diff --git a/karate-apache/src/main/java/com/intuit/karate/http/apache/LenientCookieSpec.java b/karate-apache/src/main/java/com/intuit/karate/http/apache/LenientCookieSpec.java deleted file mode 100644 index 9796598d1..000000000 --- a/karate-apache/src/main/java/com/intuit/karate/http/apache/LenientCookieSpec.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.http.apache; - -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.cookie.Cookie; -import org.apache.http.cookie.CookieOrigin; -import org.apache.http.cookie.CookieSpecProvider; -import org.apache.http.cookie.MalformedCookieException; -import org.apache.http.impl.cookie.DefaultCookieSpec; -import org.apache.http.impl.cookie.NetscapeDraftSpec; -import org.apache.http.protocol.HttpContext; - -/** - * - * @author pthomas3 - */ -public class LenientCookieSpec extends DefaultCookieSpec { - - public static String KARATE = "karate"; - - public LenientCookieSpec() { - super(new String[]{"EEE, dd-MMM-yy HH:mm:ss z", "EEE, dd MMM yyyy HH:mm:ss Z"}, false); - } - - @Override - public boolean match(Cookie cookie, CookieOrigin origin) { - return true; - } - - @Override - public void validate(Cookie cookie, CookieOrigin origin) throws MalformedCookieException { - // do nothing - } - - public static Registry registry() { - CookieSpecProvider specProvider = (HttpContext hc) -> new LenientCookieSpec(); - return RegistryBuilder.create() - .register(KARATE, specProvider).build(); - } - -} diff --git a/karate-apache/src/main/java/com/intuit/karate/http/apache/LoggingUtils.java b/karate-apache/src/main/java/com/intuit/karate/http/apache/LoggingUtils.java deleted file mode 100644 index 7b6af608a..000000000 --- a/karate-apache/src/main/java/com/intuit/karate/http/apache/LoggingUtils.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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.http.apache; - -import com.intuit.karate.http.HttpLogModifier; -import com.intuit.karate.http.HttpRequest; -import com.intuit.karate.http.HttpUtils; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; - -/** - * - * @author pthomas3 - */ -public class LoggingUtils { - - private LoggingUtils() { - // only static methods - } - - private static Collection sortKeys(Header[] headers) { - Set keys = new TreeSet<>(); - for (Header header : headers) { - keys.add(header.getName()); - } - return keys; - } - - private static void logHeaderLine(HttpLogModifier logModifier, StringBuilder sb, int id, char prefix, String key, Header[] headers) { - sb.append(id).append(' ').append(prefix).append(' ').append(key).append(": "); - if (headers.length == 1) { - if (logModifier == null) { - sb.append(headers[0].getValue()); - } else { - sb.append(logModifier.header(key, headers[0].getValue())); - } - } else { - List list = new ArrayList(headers.length); - for (Header header : headers) { - if (logModifier == null) { - list.add(header.getValue()); - } else { - list.add(logModifier.header(key, header.getValue())); - } - } - sb.append(list); - } - sb.append('\n'); - } - - public static void logHeaders(HttpLogModifier logModifier, StringBuilder sb, int id, char prefix, org.apache.http.HttpRequest request, HttpRequest actual) { - for (String key : sortKeys(request.getAllHeaders())) { - Header[] headers = request.getHeaders(key); - logHeaderLine(logModifier, sb, id, prefix, key, headers); - for (Header header : headers) { - actual.addHeader(header.getName(), header.getValue()); - } - } - } - - public static void logHeaders(HttpLogModifier logModifier, StringBuilder sb, int id, char prefix, HttpResponse response) { - for (String key : sortKeys(response.getAllHeaders())) { - Header[] headers = response.getHeaders(key); - logHeaderLine(logModifier, sb, id, prefix, key, headers); - } - } - - public static boolean isPrintable(HttpEntity entity) { - if (entity == null) { - return false; - } - return entity.getContentType() != null - && HttpUtils.isPrintable(entity.getContentType().getValue()); - } - -} diff --git a/karate-apache/src/main/java/com/intuit/karate/http/apache/RequestLoggingInterceptor.java b/karate-apache/src/main/java/com/intuit/karate/http/apache/RequestLoggingInterceptor.java deleted file mode 100644 index 5a2aa63e4..000000000 --- a/karate-apache/src/main/java/com/intuit/karate/http/apache/RequestLoggingInterceptor.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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.http.apache; - -import com.intuit.karate.FileUtils; -import com.intuit.karate.core.ScenarioContext; -import com.intuit.karate.http.HttpLogModifier; -import com.intuit.karate.http.HttpRequest; -import java.io.IOException; -import java.util.concurrent.atomic.AtomicInteger; -import org.apache.http.HttpEntity; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpException; -import org.apache.http.HttpRequestInterceptor; -import org.apache.http.protocol.HttpContext; - -/** - * - * @author pthomas3 - */ -public class RequestLoggingInterceptor implements HttpRequestInterceptor { - - private final ScenarioContext context; - private final HttpLogModifier logModifier; - private final AtomicInteger counter = new AtomicInteger(); - - public RequestLoggingInterceptor(ScenarioContext context) { - this.context = context; - logModifier = context.getConfig().getLogModifier(); - } - - public AtomicInteger getCounter() { - return counter; - } - - @Override - public void process(org.apache.http.HttpRequest request, HttpContext httpContext) throws HttpException, IOException { - HttpRequest actual = new HttpRequest(); - int id = counter.incrementAndGet(); - String uri = (String) httpContext.getAttribute(ApacheHttpClient.URI_CONTEXT_KEY); - String method = request.getRequestLine().getMethod(); - actual.setUri(uri); - actual.setMethod(method); - context.setPrevRequest(actual); - HttpLogModifier requestModifier = logModifier == null ? null : logModifier.enableForUri(uri) ? logModifier : null; - String maskedUri = requestModifier == null ? uri : requestModifier.uri(uri); - boolean showLog = !context.isReportDisabled() && context.getConfig().isShowLog(); - if (showLog) { - StringBuilder sb = new StringBuilder(); - sb.append("request:\n").append(id).append(" > ").append(method).append(' ').append(maskedUri).append('\n'); - LoggingUtils.logHeaders(requestModifier, sb, id, '>', request, actual); - if (request instanceof HttpEntityEnclosingRequest) { - HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) request; - HttpEntity entity = entityRequest.getEntity(); - if (LoggingUtils.isPrintable(entity)) { - LoggingEntityWrapper wrapper = new LoggingEntityWrapper(entity); // todo optimize, preserve if stream - String buffer = FileUtils.toString(wrapper.getContent()); - if (context.getConfig().isLogPrettyRequest()) { - buffer = FileUtils.toPrettyString(buffer); - } - if (requestModifier != null) { - buffer = requestModifier.request(uri, buffer); - } - sb.append(buffer).append('\n'); - actual.setBody(wrapper.getBytes()); - entityRequest.setEntity(wrapper); - } - } - context.logger.debug(sb.toString()); - } - // make sure this does not include the toString / logging time - actual.startTimer(); - } - -} diff --git a/karate-apache/src/main/java/com/intuit/karate/http/apache/ResponseLoggingInterceptor.java b/karate-apache/src/main/java/com/intuit/karate/http/apache/ResponseLoggingInterceptor.java deleted file mode 100644 index b6fa9025f..000000000 --- a/karate-apache/src/main/java/com/intuit/karate/http/apache/ResponseLoggingInterceptor.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.http.apache; - -import com.intuit.karate.FileUtils; -import com.intuit.karate.core.ScenarioContext; -import com.intuit.karate.http.HttpLogModifier; -import com.intuit.karate.http.HttpRequest; -import java.io.IOException; -import org.apache.http.HttpEntity; -import org.apache.http.HttpException; -import org.apache.http.HttpResponse; -import org.apache.http.HttpResponseInterceptor; -import org.apache.http.protocol.HttpContext; - -/** - * - * @author pthomas3 - */ -public class ResponseLoggingInterceptor implements HttpResponseInterceptor { - - private final ScenarioContext context; - private final HttpLogModifier logModifier; - private final RequestLoggingInterceptor requestInterceptor; - - public ResponseLoggingInterceptor(RequestLoggingInterceptor requestInterceptor, ScenarioContext context) { - this.requestInterceptor = requestInterceptor; - this.context = context; - logModifier = context.getConfig().getLogModifier(); - } - - @Override - public void process(HttpResponse response, HttpContext httpContext) throws HttpException, IOException { - HttpRequest actual = context.getPrevRequest(); - actual.stopTimer(); - boolean showLog = !context.isReportDisabled() && context.getConfig().isShowLog(); - if (!showLog) { - return; - } - int id = requestInterceptor.getCounter().get(); - StringBuilder sb = new StringBuilder(); - sb.append("response time in milliseconds: ").append(actual.getResponseTimeFormatted()).append('\n'); - sb.append(id).append(" < ").append(response.getStatusLine().getStatusCode()).append('\n'); - HttpLogModifier responseModifier = logModifier == null ? null : logModifier.enableForUri(actual.getUri()) ? logModifier : null; - LoggingUtils.logHeaders(responseModifier, sb, id, '<', response); - HttpEntity entity = response.getEntity(); - if (LoggingUtils.isPrintable(entity)) { - LoggingEntityWrapper wrapper = new LoggingEntityWrapper(entity); - String buffer = FileUtils.toString(wrapper.getContent()); - if (context.getConfig().isLogPrettyResponse()) { - buffer = FileUtils.toPrettyString(buffer); - } - if (responseModifier != null) { - buffer = responseModifier.response(actual.getUri(), buffer); - } - sb.append(buffer).append('\n'); - response.setEntity(wrapper); - } - context.logger.debug(sb.toString()); - } - -} diff --git a/karate-apache/src/main/resources/karate-http.properties b/karate-apache/src/main/resources/karate-http.properties deleted file mode 100644 index e4f7cfd31..000000000 --- a/karate-apache/src/main/resources/karate-http.properties +++ /dev/null @@ -1 +0,0 @@ -client.class=com.intuit.karate.http.apache.ApacheHttpClient diff --git a/karate-apache/src/test/java/com/intuit/karate/http/apache/ApacheHttpClientTest.java b/karate-apache/src/test/java/com/intuit/karate/http/apache/ApacheHttpClientTest.java deleted file mode 100644 index 1fc56ac3d..000000000 --- a/karate-apache/src/test/java/com/intuit/karate/http/apache/ApacheHttpClientTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.intuit.karate.http.apache; - -import com.intuit.karate.CallContext; -import com.intuit.karate.Config; -import com.intuit.karate.core.FeatureContext; -import com.intuit.karate.core.ScenarioContext; -import com.intuit.karate.http.Cookie; -import org.apache.http.client.CookieStore; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.Field; -import java.time.ZonedDateTime; -import java.util.LinkedHashMap; -import java.util.Map; - -import static com.intuit.karate.http.Cookie.*; -import static com.intuit.karate.http.HttpClient.construct; -import static org.junit.Assert.assertEquals; - -public class ApacheHttpClientTest { - - private static final Logger logger = LoggerFactory.getLogger(ApacheHttpClientTest.class); - - private ScenarioContext getContext() { - FeatureContext featureContext = FeatureContext.forEnv(); - CallContext callContext = new CallContext(null, true); - return new ScenarioContext(featureContext, callContext, null, null); - } - - private Config getConfig() { - return new Config(); - } - - private Map getCookieMapWithExpiredDate() { - ZonedDateTime currentDate = ZonedDateTime.now(); - Map cookieMap = new LinkedHashMap<>(); - cookieMap.put(NAME, "testCookie"); - cookieMap.put(VALUE, "tck"); - cookieMap.put(DOMAIN, ".com"); - cookieMap.put(PATH, "/"); - cookieMap.put(EXPIRES,currentDate.minusDays(1).format(DTFMTR_RFC1123)); - return cookieMap; - } - - private Map getCookieMapWithNonExpiredDate() { - ZonedDateTime currentDate = ZonedDateTime.now(); - Map cookieMap = new LinkedHashMap<>(); - cookieMap.put(NAME, "testCookie"); - cookieMap.put(VALUE, "tck"); - cookieMap.put(DOMAIN, ".com"); - cookieMap.put(PATH, "/"); - cookieMap.put(EXPIRES, currentDate.plusDays(1).format(DTFMTR_RFC1123)); - return cookieMap; - } - - @Test - public void testExpiredCookieIsRemoved() throws NoSuchFieldException, IllegalAccessException { - com.intuit.karate.http.Cookie c = new Cookie(getCookieMapWithExpiredDate()); - ApacheHttpClient httpClient = (ApacheHttpClient) construct(getConfig(), getContext()); - httpClient.buildCookie(c); - - Field cookieStoreField = httpClient.getClass().getDeclaredField("cookieStore"); - cookieStoreField.setAccessible(true); - CookieStore fieldValue = (CookieStore) cookieStoreField.get(httpClient); - assertEquals(0, fieldValue.getCookies().size()); - } - - @Test - public void testNonExpiredCookieIsPersisted() throws NoSuchFieldException, IllegalAccessException { - com.intuit.karate.http.Cookie c = new Cookie(getCookieMapWithNonExpiredDate()); - ApacheHttpClient httpClient = (ApacheHttpClient) construct(getConfig(), getContext()); - httpClient.buildCookie(c); - - Field cookieStoreField = httpClient.getClass().getDeclaredField("cookieStore"); - cookieStoreField.setAccessible(true); - CookieStore fieldValue = (CookieStore) cookieStoreField.get(httpClient); - assertEquals(1, fieldValue.getCookies().size()); - } -} - - diff --git a/karate-apache/src/test/java/com/intuit/karate/http/apache/ApacheHttpUtilsTest.java b/karate-apache/src/test/java/com/intuit/karate/http/apache/ApacheHttpUtilsTest.java deleted file mode 100644 index 85bacb03f..000000000 --- a/karate-apache/src/test/java/com/intuit/karate/http/apache/ApacheHttpUtilsTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.intuit.karate.http.apache; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; - -import java.nio.charset.StandardCharsets; - -import org.apache.http.HttpEntity; -import org.junit.Test; - -/** @author nsehgal */ -public class ApacheHttpUtilsTest { - - @Test - public void testContentTypeWithQuotes() { - final String originalContentType = - "multipart/related; charset=UTF-8; boundary=\"----=_Part_19_1913847857.1592612068756\"; type=\"application/xop+xml\"; start-info=\"text/xml\""; - - HttpEntity httpEntity = - ApacheHttpUtils.getEntity( - "content is not important", originalContentType, StandardCharsets.UTF_8); - - assertEquals(originalContentType, httpEntity.getContentType().getValue()); - } - - @Test - public void testContentTypeWithoutQuotes() { - final String originalContentType = - "multipart/related; charset=UTF-8; boundary=----=_Part_19_1913847857.1592612068756; type=application/xop+xml; start-info=text/xml"; - - final String expectedContentType = - "multipart/related; charset=UTF-8; boundary=\"----=_Part_19_1913847857.1592612068756\"; type=\"application/xop+xml\"; start-info=\"text/xml\""; - - HttpEntity httpEntity = - ApacheHttpUtils.getEntity( - "content is not important", originalContentType, StandardCharsets.UTF_8); - - assertNotEquals(originalContentType, httpEntity.getContentType().getValue()); - assertEquals(expectedContentType, httpEntity.getContentType().getValue()); - } - - @Test - public void testContentTypeWithoutQuotesCharsetInLast() { - final String originalContentType = - "multipart/related; boundary=----=_Part_19_1913847857.1592612068756; type=application/xop+xml; start-info=text/xml; charset=UTF-8"; - - final String expectedContentType = - "multipart/related; boundary=\"----=_Part_19_1913847857.1592612068756\"; type=\"application/xop+xml\"; start-info=\"text/xml\"; charset=UTF-8"; - - HttpEntity httpEntity = - ApacheHttpUtils.getEntity( - "content is not important", originalContentType, StandardCharsets.UTF_8); - - assertNotEquals(originalContentType, httpEntity.getContentType().getValue()); - assertEquals(expectedContentType, httpEntity.getContentType().getValue()); - } - - @Test - public void testContentTypeWithCustomCharset() { - final String originalContentType = - "multipart/related; boundary=----=_Part_19_1913847857.1592612068756; type=application/xop+xml; start-info=text/xml"; - - final String expectedContentType = - "multipart/related; boundary=\"----=_Part_19_1913847857.1592612068756\"; type=\"application/xop+xml\"; start-info=\"text/xml\"; charset=UTF-8"; - - HttpEntity httpEntity = - ApacheHttpUtils.getEntity( - "content is not important", originalContentType, StandardCharsets.UTF_8); - - assertNotEquals(originalContentType, httpEntity.getContentType().getValue()); - assertEquals(expectedContentType, httpEntity.getContentType().getValue()); - } -} diff --git a/karate-apache/src/test/resources/karate-config.js b/karate-apache/src/test/resources/karate-config.js deleted file mode 100755 index dea6405ed..000000000 --- a/karate-apache/src/test/resources/karate-config.js +++ /dev/null @@ -1,3 +0,0 @@ -function fn() { - return { someConfig: 'someValue' } -} \ No newline at end of file diff --git a/karate-apache/src/test/resources/karate-http.properties b/karate-apache/src/test/resources/karate-http.properties deleted file mode 100644 index cd3832389..000000000 --- a/karate-apache/src/test/resources/karate-http.properties +++ /dev/null @@ -1,2 +0,0 @@ -client.class=com.intuit.karate.http.apache.ApacheHttpClient - diff --git a/karate-archetype/src/main/resources/archetype-resources/pom.xml b/karate-archetype/src/main/resources/archetype-resources/pom.xml index 887d2eb5f..755de4084 100755 --- a/karate-archetype/src/main/resources/archetype-resources/pom.xml +++ b/karate-archetype/src/main/resources/archetype-resources/pom.xml @@ -14,13 +14,7 @@ 0.9.6 - - - com.intuit.karate - karate-apache - ${karate.version} - test - + com.intuit.karate karate-junit5 diff --git a/karate-core/pom.xml b/karate-core/pom.xml index fb37c9f9e..6ef17c53d 100644 --- a/karate-core/pom.xml +++ b/karate-core/pom.xml @@ -12,21 +12,58 @@ 4.7.1 - 4.1.50.Final - 2.30 + 20.3.0 - io.netty - netty-handler - ${netty.version} - + org.graalvm.js + js-scriptengine + ${graal.version} + + + org.graalvm.js + js + ${graal.version} + runtime + + + org.thymeleaf + thymeleaf + 3.0.11.RELEASE + + + com.linecorp.armeria + armeria + 1.3.0 + + + org.apache.httpcomponents + httpclient + 4.5.13 + + + commons-logging + commons-logging + + + commons-codec + commons-codec + + + + + ch.qos.logback + logback-classic + 1.2.3 + - io.netty - netty-codec-http - ${netty.version} - + org.slf4j + jcl-over-slf4j + + 1.7.25 + runtime + org.antlr antlr4-runtime @@ -59,29 +96,29 @@ org.yaml snakeyaml - 1.26 + 1.27 de.siegmar fastcsv - 1.0.3 + 1.0.4 info.picocli picocli - 4.0.3 + 4.5.2 - org.glassfish.jersey.core - jersey-common - ${jersey.version} - + io.github.classgraph + classgraph + 4.8.93 + - junit - junit - ${junit.version} + org.junit.jupiter + junit-jupiter + ${junit5.version} test - + @@ -133,6 +170,24 @@ pre-release + + org.apache.maven.plugins + maven-dependency-plugin + 3.1.2 + + + unpack-dependencies + validate + + unpack-dependencies + + + /META-INF/native/* + target/shade + + + + org.apache.maven.plugins maven-shade-plugin @@ -146,32 +201,144 @@ + com.linecorp.armeria:* + io.micrometer:* + org.reactivestreams:* io.netty:* + io.netty.incubator:* + org.apache.httpcomponents:* org.antlr:* - org.glassfish.jersey.core:* - + + + com.linecorp + karate.com.linecorp + io.netty - io.netty.karate + karate.io.netty - org.antlr.v4 - org.antlr.karate - + org.apache.http + karate.org.apache.http + - org.glassfish.jersey - org.glassfish.jersey.karate - + org.antlr.v4 + karate.org.antlr.v4 + + + + io.netty.incubator:netty-incubator-transport-native-io_uring + + META-INF/native/libnetty_transport_native_io_uring_x86_64.so + + + + io.netty:netty-transport-native-epoll + + META-INF/native/libnetty_transport_native_epoll_x86_64.so + + + + io.netty:netty-tcnative-boringssl-static + + META-INF/native/libnetty_tcnative_linux_x86_64.so + META-INF/native/libnetty_tcnative_linux_aarch64.so + META-INF/native/libnetty_tcnative_osx_x86_64.jnilib + META-INF/native/netty_tcnative_windows_x86_64.dll + + + + + + com.intuit.karate.Main + + + META-INF/native/libkarate_netty_transport_native_io_uring_x86_64.so + target/shade/META-INF/native/libnetty_transport_native_io_uring_x86_64.so + + + META-INF/native/libkarate_netty_transport_native_epoll_x86_64.so + target/shade/META-INF/native/libnetty_transport_native_epoll_x86_64.so + + + META-INF/native/libkarate_netty_tcnative_linux_x86_64.so + target/shade/META-INF/native/libnetty_tcnative_linux_x86_64.so + + + META-INF/native/libkarate_netty_tcnative_linux_aarch64.so + target/shade/META-INF/native/libnetty_tcnative_linux_aarch64.so + + + META-INF/native/libkarate_netty_tcnative_osx_x86_64.jnilib + target/shade/META-INF/native/libnetty_tcnative_osx_x86_64.jnilib + + + META-INF/native/karate_netty_tcnative_windows_x86_64.dll + target/shade/META-INF/native/netty_tcnative_windows_x86_64.dll + + - + + + fatjar + + + + org.apache.maven.plugins + maven-shade-plugin + ${maven.shade.version} + + + package + + shade + + + karate-${project.version} + + + *:* + + + + + com.intuit.karate.Main + + + + + + + + maven-assembly-plugin + 3.3.0 + + + src/assembly/bin.xml + + karate-${project.version} + false + + + + package + + single + + + + + + + diff --git a/karate-netty/src/assembly/bin.xml b/karate-core/src/assembly/bin.xml similarity index 100% rename from karate-netty/src/assembly/bin.xml rename to karate-core/src/assembly/bin.xml diff --git a/karate-core/src/main/java/com/intuit/karate/Actions.java b/karate-core/src/main/java/com/intuit/karate/Actions.java index 2f4af6507..d56743427 100644 --- a/karate-core/src/main/java/com/intuit/karate/Actions.java +++ b/karate-core/src/main/java/com/intuit/karate/Actions.java @@ -32,23 +32,29 @@ */ public interface Actions { - void assertTrue(String expression); + boolean isFailed(); + + Throwable getFailedReason(); + + boolean isAborted(); + + void assertTrue(String exp); void call(String line); void callonce(String line); - - void csv(String name, String expression); - void json(String name, String expression); + void csv(String name, String exp); + + void json(String name, String exp); - void string(String name, String expression); + void string(String name, String exp); - void xml(String name, String expression); + void xml(String name, String exp); - void xmlstring(String name, String expression); - - void bytes(String name, String expression); + void xmlstring(String name, String exp); + + void bytes(String name, String exp); void configure(String key, String exp); @@ -56,53 +62,55 @@ public interface Actions { void cookie(String name, String value); - void cookies(String expr); + void cookies(String exp); - void copy(String name, String expression); + void copy(String name, String exp); - void def(String name, String expression); + void def(String name, String exp); - void defDocstring(String name, String expression); + void defDocstring(String name, String exp); void eval(String exp); void evalDocstring(String exp); - - void eval(String name, String dotOrParen, String expression); - - void evalIf(String expression); - - void formField(String name, List values); - void formFields(String expr); + void eval(String name, String dotOrParen, String exp); + + void evalIf(String exp); + + void formField(String name, String exp); + + void formFields(String exp); - void header(String name, List values); + void header(String name, String exp); - void headers(String expr); + void headers(String exp); - void match(String expression, String op1, String op2, String rhs); + void listen(String exp); + + void match(String exp, String op1, String op2, String rhs); void method(String method); - + void retry(String until); void multipartEntity(String value); - void multipartFiles(String expr); + void multipartFiles(String exp); void multipartField(String name, String value); - void multipartFields(String expr); + void multipartFields(String exp); void multipartFile(String name, String value); - void param(String name, List values); + void param(String name, String exp); - void params(String expr); + void params(String exp); - void path(List paths); + void path(String exp); - void print(List exps); + void print(String exp); void remove(String name, String path); @@ -126,16 +134,20 @@ public interface Actions { void table(String name, List> table); - void text(String name, String expression); + void text(String name, String exp); + + void url(String exp); + + void yaml(String name, String exp); + + void doc(String exp); + + void docDocstring(String exp); - void url(String expression); - - void yaml(String name, String expression); - //========================================================================== // - void driver(String expression); - - void robot(String expression); + void driver(String exp); + + void robot(String exp); } diff --git a/karate-core/src/main/java/com/intuit/karate/CallContext.java b/karate-core/src/main/java/com/intuit/karate/CallContext.java deleted file mode 100644 index 464cc0fbf..000000000 --- a/karate-core/src/main/java/com/intuit/karate/CallContext.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.core.ExecutionHook; -import com.intuit.karate.core.ExecutionHookFactory; -import com.intuit.karate.core.Feature; -import com.intuit.karate.core.ScenarioContext; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Map; - -/** - * - * @author pthomas3 - */ -public class CallContext { - - public final Feature feature; - public final ScenarioContext context; - public final int callDepth; - public final Map callArg; - public final boolean reuseParentContext; - public final boolean evalKarateConfig; - public final int loopIndex; - public final String httpClientClass; - public final Collection executionHooks = new ArrayList(); - public final ExecutionHookFactory hookFactory; - public final boolean perfMode; - - public static CallContext forCall(Feature feature, ScenarioContext context, Map callArg, - int loopIndex, boolean reuseParentConfig) { - return new CallContext(feature, context, context.callDepth + 1, callArg, loopIndex, - reuseParentConfig, false, null, context.executionHooks, null, context.perfMode); - } - - public static CallContext forAsync(Feature feature, Collection hooks, ExecutionHookFactory hookFactory, - Map arg, boolean perfMode) { - return new CallContext(feature, null, 0, arg, -1, false, true, null, hooks, hookFactory, perfMode); - } - - public boolean isCalled() { - return callDepth > 0; - } - - private boolean resolved; - - public Collection resolveHooks() { - if (hookFactory == null || resolved) { - return executionHooks; - } - resolved = true; - executionHooks.add(hookFactory.create()); - return executionHooks; - } - - public CallContext(Map callArg, boolean evalKarateConfig, ExecutionHook... hooks) { - this(null, null, 0, callArg, -1, false, evalKarateConfig, null, hooks.length == 0 ? null : Arrays.asList(hooks), null, false); - } - - public CallContext(Feature feature, ScenarioContext context, int callDepth, Map callArg, int loopIndex, - boolean reuseParentContext, boolean evalKarateConfig, String httpClientClass, - Collection executionHooks, ExecutionHookFactory hookFactory, boolean perfMode) { - this.feature = feature; - this.context = context; - this.callDepth = callDepth; - this.callArg = callArg; - this.loopIndex = loopIndex; - this.reuseParentContext = reuseParentContext; - this.evalKarateConfig = evalKarateConfig; - this.httpClientClass = httpClientClass; - if (executionHooks != null) { - this.executionHooks.addAll(executionHooks); - } - this.hookFactory = hookFactory; - this.perfMode = perfMode; - } - -} diff --git a/karate-core/src/main/java/com/intuit/karate/CallResult.java b/karate-core/src/main/java/com/intuit/karate/Constants.java similarity index 69% rename from karate-core/src/main/java/com/intuit/karate/CallResult.java rename to karate-core/src/main/java/com/intuit/karate/Constants.java index a2b28143d..eaac13fb8 100644 --- a/karate-core/src/main/java/com/intuit/karate/CallResult.java +++ b/karate-core/src/main/java/com/intuit/karate/Constants.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright 2017 Intuit Inc. + * Copyright 2020 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 @@ -27,16 +27,18 @@ * * @author pthomas3 */ -public class CallResult { - - protected final ScriptValue value; - protected final Config config; - protected final ScriptValueMap vars; - - public CallResult(ScriptValue value, Config config, ScriptValueMap vars) { - this.value = value; - this.config = config; - this.vars = vars; +public class Constants { + + private Constants() { + // only static methods } + + public static final String KARATE_ENV = "karate.env"; + public static final String KARATE_CONFIG_DIR = "karate.config.dir"; + public static final String KARATE_OUTPUT_DIR = "karate.output.dir"; + public static final String KARATE_OPTIONS = "karate.options"; + public static final String KARATE_REPORTS = "karate-reports"; + public static final byte[] ZERO_BYTES = new byte[0]; + } diff --git a/karate-core/src/main/java/com/intuit/karate/FileUtils.java b/karate-core/src/main/java/com/intuit/karate/FileUtils.java index db7c37562..c91585636 100755 --- a/karate-core/src/main/java/com/intuit/karate/FileUtils.java +++ b/karate-core/src/main/java/com/intuit/karate/FileUtils.java @@ -23,11 +23,7 @@ */ package com.intuit.karate; -import com.intuit.karate.core.ScenarioContext; import com.intuit.karate.core.Feature; -import com.intuit.karate.core.FeatureParser; -import com.intuit.karate.exception.KarateFileNotFoundException; -import com.jayway.jsonpath.DocumentContext; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -36,28 +32,11 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.net.URI; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.Comparator; import java.util.Properties; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Stream; import org.slf4j.LoggerFactory; /** @@ -66,106 +45,29 @@ */ public class FileUtils { - private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(FileUtils.class); - - public static final Charset UTF8 = StandardCharsets.UTF_8; - public static final byte[] EMPTY_BYTES = new byte[]{}; - - private static final String CLASSPATH = "classpath"; - - public static final String CLASSPATH_COLON = CLASSPATH + ":"; - private static final String DOT_FEATURE = ".feature"; - public static final String THIS_COLON = "this:"; - public static final String FILE_COLON = "file:"; - public static final String SRC_TEST_JAVA = "src/test/java"; - public static final String SRC_TEST_RESOURCES = "src/test/resources"; - private static final ClassLoader CLASS_LOADER = FileUtils.class.getClassLoader(); + private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(FileUtils.class); private FileUtils() { // only static methods } + + public static final String KARATE_VERSION; - public static final boolean isClassPath(String text) { - return text.startsWith(CLASSPATH_COLON); - } - - public static final boolean isFilePath(String text) { - return text.startsWith(FILE_COLON); - } - - public static final boolean isThisPath(String text) { - return text.startsWith(THIS_COLON); - } - - public static final boolean isJsonFile(String text) { - return text.endsWith(".json"); - } - - public static final boolean isJavaScriptFile(String text) { - return text.endsWith(".js"); - } - - public static final boolean isYamlFile(String text) { - return text.endsWith(".yaml") || text.endsWith(".yml"); - } - - public static final boolean isXmlFile(String text) { - return text.endsWith(".xml"); - } - - public static final boolean isTextFile(String text) { - return text.endsWith(".txt"); - } - - public static final boolean isCsvFile(String text) { - return text.endsWith(".csv"); - } - - public static final boolean isGraphQlFile(String text) { - return text.endsWith(".graphql") || text.endsWith(".gql"); - } - - public static final boolean isFeatureFile(String text) { - return text.endsWith(".feature"); - } - - public static ScriptValue readFile(String text, ScenarioContext context) { - StringUtils.Pair pair = parsePathAndTags(text); - text = pair.left; - if (isJsonFile(text) || isXmlFile(text) || isJavaScriptFile(text)) { - String contents = readFileAsString(text, context); - contents = StringUtils.fixJavaScriptFunction(contents); - ScriptValue temp = Script.evalKarateExpression(contents, context); - return new ScriptValue(temp.getValue(), text); - } else if (isTextFile(text) || isGraphQlFile(text)) { - String contents = readFileAsString(text, context); - return new ScriptValue(contents, text); - } else if (isFeatureFile(text)) { - Resource fr = toResource(text, context); - Feature feature = FeatureParser.parse(fr); - feature.setCallTag(pair.right); - return new ScriptValue(feature, text); - } else if (isCsvFile(text)) { - String contents = readFileAsString(text, context); - DocumentContext doc = JsonUtils.fromCsv(contents); - return new ScriptValue(doc, text); - } else if (isYamlFile(text)) { - String contents = readFileAsString(text, context); - DocumentContext doc = JsonUtils.fromYaml(contents); - return new ScriptValue(doc, text); - } else { - InputStream is = readFileAsStream(text, context); - return new ScriptValue(is, text); + static { + Properties props = new Properties(); + InputStream stream = FileUtils.class.getResourceAsStream("/karate-meta.properties"); + String value; + try { + props.load(stream); + stream.close(); + value = (String) props.get("karate.version"); + } catch (IOException e) { + value = "(unknown)"; } - } - - public static String removePrefix(String text) { - if (text == null) { - return null; - } - int pos = text.indexOf(':'); - return pos == -1 ? text : text.substring(pos + 1); - } + KARATE_VERSION = value; + } + + public static final File WORKING_DIR = new File("").getAbsoluteFile(); public static StringUtils.Pair parsePathAndTags(String text) { int pos = text.indexOf('@'); @@ -181,76 +83,11 @@ public static StringUtils.Pair parsePathAndTags(String text) { public static Feature parseFeatureAndCallTag(String path) { StringUtils.Pair pair = parsePathAndTags(path); - Feature feature = FeatureParser.parse(pair.left); + Feature feature = Feature.read(pair.left); feature.setCallTag(pair.right); return feature; } - public static Resource toResource(String path, ScenarioContext context) { - if (isClassPath(path)) { - return new Resource(path, context.classLoader); - } else if (isFilePath(path)) { - String temp = removePrefix(path); - return new Resource(new File(temp), path, context.classLoader); - } else if (isThisPath(path)) { - String temp = removePrefix(path); - Path parentPath = context.featureContext.parentPath; - Path childPath = parentPath.resolve(temp); - return new Resource(childPath, context.classLoader); - } else { - try { - Path parentPath = context.rootFeatureContext.parentPath; - Path childPath = parentPath.resolve(path); - return new Resource(childPath, context.classLoader); - } catch (Exception e) { - LOGGER.error("feature relative path resolution failed: {}", e.getMessage()); - throw e; - } - } - } - - public static String readFileAsString(String path, ScenarioContext context) { - return toString(readFileAsStream(path, context)); - } - - public static InputStream readFileAsStream(String path, ScenarioContext context) { - try { - return toResource(path, context).getStream(); - } catch (Exception e) { - InputStream inputStream = context.getResourceAsStream(removePrefix(path)); - if (inputStream == null) { - // attempt to get the file using the class classloader - // workaround for spring boot - inputStream = FileUtils.class.getClassLoader().getResourceAsStream(path); - } - if (inputStream == null) { - String message = String.format("could not find or read file: %s", path); - context.logger.trace("{}", message); - throw new KarateFileNotFoundException(message); - } - return inputStream; - } - } - - public static String toPackageQualifiedName(String path) { - path = removePrefix(path); - path = path.replace('/', '.'); - if (path.contains(":\\")) { // to remove windows drive letter and colon - path = removePrefix(path); - } - if (path.indexOf('\\') != -1) { // for windows paths - path = path.replace('\\', '.'); - } - String packagePath = path.replace("..", ""); - if (packagePath.startsWith(".")) { - packagePath = packagePath.substring(1); - } - if (packagePath.endsWith(DOT_FEATURE)) { - packagePath = packagePath.substring(0, packagePath.length() - 8); - } - return packagePath; - } - public static String toString(File file) { try { return toString(new FileInputStream(file)); @@ -261,26 +98,12 @@ public static String toString(File file) { public static String toString(InputStream is) { try { - return toByteStream(is).toString(UTF8.name()); + return toByteStream(is).toString(StandardCharsets.UTF_8.name()); } catch (Exception e) { throw new RuntimeException(e); } } - public static String toPrettyString(String raw) { - raw = StringUtils.trimToEmpty(raw); - try { - if (Script.isJson(raw)) { - return JsonUtils.toPrettyJsonString(JsonUtils.toJsonDoc(raw)); - } else if (Script.isXml(raw)) { - return XmlUtils.toString(XmlUtils.toXmlDoc(raw), true); - } - } catch (Exception e) { - LOGGER.warn("parsing failed: {}", e.getMessage()); - } - return raw; - } - public static byte[] toBytes(InputStream is) { return toByteStream(is).toByteArray(); } @@ -303,14 +126,14 @@ public static String toString(byte[] bytes) { if (bytes == null) { return null; } - return new String(bytes, UTF8); + return new String(bytes, StandardCharsets.UTF_8); } public static byte[] toBytes(String string) { if (string == null) { return null; } - return string.getBytes(UTF8); + return string.getBytes(StandardCharsets.UTF_8); } public static void copy(File src, File dest) { @@ -336,45 +159,22 @@ public static void writeToFile(File file, byte[] data) { } public static void writeToFile(File file, String data) { - writeToFile(file, data.getBytes(UTF8)); + writeToFile(file, data.getBytes(StandardCharsets.UTF_8)); } public static InputStream toInputStream(String text) { - return new ByteArrayInputStream(text.getBytes(UTF8)); + return new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)); } - public static String removeFileExtension(String path) { - int pos = path.lastIndexOf('.'); - if (pos == -1) { - return path; - } else { - return path.substring(0, pos); - } - } - - public static String replaceFileExtension(String path, String extension) { - int pos = path.lastIndexOf('.'); - if (pos == -1) { - return path + '.' + extension; - } else { - return path.substring(0, pos + 1) + extension; - } - } - - private static final String UNKNOWN = "(unknown)"; - - public static String getKarateVersion() { - InputStream stream = FileUtils.class.getResourceAsStream("/karate-meta.properties"); - if (stream == null) { - return UNKNOWN; - } - Properties props = new Properties(); + public static void deleteDirectory(File file) { + Path pathToBeDeleted = file.toPath(); try { - props.load(stream); - stream.close(); - return (String) props.get("karate.version"); - } catch (IOException e) { - return UNKNOWN; + Files.walk(pathToBeDeleted) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } catch (Exception e) { + throw new RuntimeException(); } } @@ -394,331 +194,8 @@ public static void renameFileIfZeroBytes(String fileName) { } } - public static String toStandardPath(String path) { - if (path == null) { - return null; - } - path = path.replace('\\', '/'); - return path.startsWith("/") ? path.substring(1) : path; - } - - public static String toRelativeClassPath(Path path, ClassLoader cl) { - if (isJarPath(path.toUri())) { - return CLASSPATH_COLON + toStandardPath(path.toString()); - } - for (URL url : getAllClassPathUrls(cl)) { - Path rootPath = urlToPath(url, null); - if (rootPath != null && path.startsWith(rootPath)) { - Path relativePath = rootPath.relativize(path); - return CLASSPATH_COLON + toStandardPath(relativePath.toString()); - } - } - // we didn't find this on the classpath, fall back to absolute - return path.toString().replace('\\', '/'); - } - - public static File getDirContaining(Class clazz) { - Path path = getPathContaining(clazz); - return path.toFile(); - } - - public static Path getPathContaining(Class clazz) { - String relative = packageAsPath(clazz); - URL url = clazz.getClassLoader().getResource(relative); - return urlToPath(url, null); - } - - private static String packageAsPath(Class clazz) { - Package p = clazz.getPackage(); - String relative = ""; - if (p != null) { - relative = p.getName().replace('.', '/'); - } - return relative; - } - - public static File getFileRelativeTo(Class clazz, String path) { - Path dirPath = getPathContaining(clazz); - File file = new File(dirPath + File.separator + path); - if (file.exists()) { - return file; - } - try { - URL relativePath = clazz.getClassLoader().getResource(packageAsPath(clazz) + File.separator + path); - return Paths.get(relativePath.toURI()).toFile(); - } catch (Exception e) { - throw new IllegalArgumentException(String.format("Cannot resolve path '%s' relative to class '%s' ", path, clazz.getName()), e); - } - } - - public static String toRelativeClassPath(Class clazz) { - Path dirPath = getPathContaining(clazz); - return toRelativeClassPath(dirPath, clazz.getClassLoader()); - } - - public static Path fromRelativeClassPath(String relativePath, ClassLoader cl) { - relativePath = removePrefix(relativePath); - URL url = cl.getResource(relativePath); - if (url == null) { // assume this is a "real" path to a file - return new File(relativePath).toPath(); - } - return urlToPath(url, relativePath); - } - - public static Path fromRelativeClassPath(String relativePath, Path parentPath) { - boolean classpath = isClassPath(relativePath); - relativePath = removePrefix(relativePath); - if (classpath) { // use context file-system resolution - return parentPath.resolve(relativePath); - } else { - return new File(relativePath).toPath(); - } - } - - public static List scanForFeatureFilesOnClassPath(ClassLoader cl) { - return scanForFeatureFiles(true, CLASSPATH_COLON, cl); - } - - public static List scanForFeatureFiles(List paths, ClassLoader cl) { - if (paths == null) { - return Collections.EMPTY_LIST; - } - List list = new ArrayList(); - for (String path : paths) { - boolean classpath = isClassPath(path); - list.addAll(scanForFeatureFiles(classpath, path, cl)); - } - return list; - } - - public static List scanForFeatureFiles(List paths, Class clazz) { - if (clazz == null) { - return scanForFeatureFiles(paths, Thread.currentThread().getContextClassLoader()); - } - // this resolves paths relative to the passed-in class - List list = new ArrayList(); - for (String path : paths) { - boolean classpath = isClassPath(path); - if (!classpath) { // convert from relative path - if (!path.endsWith(".feature")) { - path = path + ".feature"; - } - path = toRelativeClassPath(clazz) + "/" + path; - } - list.addAll(scanForFeatureFiles(true, path, clazz.getClassLoader())); - } - return list; - } - - public static boolean isJarPath(URI uri) { - return uri.toString().contains("!/"); - } - - public static Path urlToPath(URL url, String relativePath) { - try { - URI uri = url.toURI(); - if (isJarPath(uri)) { - FileSystem fs = getFileSystem(uri); - Path path = fs.getRootDirectories().iterator().next(); - if (relativePath != null) { - return path.resolve(relativePath); - } else { - return path; - } - } else { - return Paths.get(uri); - } - } catch (Exception e) { - LOGGER.trace("invalid path: {}", e.getMessage()); - return null; - } - } - - public static List getAllClassPathUrls(ClassLoader classLoader) { - try { - List list = new ArrayList(); - Enumeration iterator = classLoader.getResources(""); - while (iterator.hasMoreElements()) { - URL url = iterator.nextElement(); - list.add(url); - } - if (classLoader instanceof URLClassLoader) { - for (URL u : ((URLClassLoader) classLoader).getURLs()) { - URL url = new URL("jar:" + u + "!/"); - list.add(url); - } - } else { - String classpath = System.getProperty("java.class.path"); - if (classpath != null && !classpath.isEmpty()) { - String[] classpathEntries = classpath.split(File.pathSeparator); - for (String classpathEntry : classpathEntries) { - if (classpathEntry.endsWith(".jar")) { - String entryWithForwardSlashes = classpathEntry.replaceAll("\\\\", "/"); - boolean startsWithSlash = entryWithForwardSlashes.startsWith("/"); - URL url = new URL("jar:file:" + (startsWithSlash ? "" : "/") + entryWithForwardSlashes + "!/"); - list.add(url); - } - } - } - } - return list; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static final Map FILE_SYSTEM_CACHE = new HashMap(); - - private static FileSystem getFileSystem(URI uri) { - FileSystem fs = FILE_SYSTEM_CACHE.get(uri); - if (fs != null) { - return fs; - } - // java nio has some problems here ! - synchronized (FILE_SYSTEM_CACHE) { - fs = FILE_SYSTEM_CACHE.get(uri); // retry with lock - if (fs != null) { - return fs; - } - try { - fs = FileSystems.getFileSystem(uri); - } catch (Exception e) { - try { - LOGGER.trace("creating file system for URI: {} - {}", uri, e.getMessage()); - fs = FileSystems.newFileSystem(uri, Collections.emptyMap()); - } catch (IOException ioe) { - LOGGER.error("file system creation failed for URI: {} - {}", uri, ioe.getMessage()); - throw new RuntimeException(ioe); - } - } - FILE_SYSTEM_CACHE.put(uri, fs); - return fs; - } - } - - public static List scanForFeatureFiles(boolean classpath, String searchPath, ClassLoader cl) { - List files = new ArrayList(); - if (classpath) { - searchPath = removePrefix(searchPath); - for (URL url : getAllClassPathUrls(cl)) { - collectFeatureFiles(url, searchPath, files, cl); - } - return files; - } else { - collectFeatureFiles(null, searchPath, files, cl); - return files; - } - } - - private static void collectFeatureFiles(URL url, String searchPath, List files, ClassLoader cl) { - boolean classpath = url != null; - int colonPos = searchPath.lastIndexOf(':'); - int line = -1; - if (colonPos > 1) { // line number has been appended, and not windows "C:\foo" kind of path - try { - line = Integer.valueOf(searchPath.substring(colonPos + 1)); - searchPath = searchPath.substring(0, colonPos); - } catch (Exception e) { - // defensive coding, abort attempting to parse line number - } - } - Path rootPath; - Path search; - if (classpath) { - File test = new File(searchPath); - if (test.exists() && test.isAbsolute()) { - // although the classpath: prefix was used this is an absolute path ! fix - classpath = false; - } - } - if (classpath) { - rootPath = urlToPath(url, null); - if (rootPath == null) { // windows edge case - return; - } - search = rootPath.resolve(searchPath); - } else { - rootPath = new File(".").getAbsoluteFile().toPath(); - search = Paths.get(searchPath); - } - Stream stream; - try { - stream = Files.walk(search); - } catch (IOException e) { // NoSuchFileException - return; - } - for (Iterator paths = stream.iterator(); paths.hasNext();) { - Path path = paths.next(); - Path fileName = path.getFileName(); - if (fileName != null && fileName.toString().endsWith(".feature")) { - if (!files.isEmpty()) { - // since the classpath search paths are in pairs or groups - // skip if we found this already - // else duplication happens if we use absolute paths as search paths - Path prev = files.get(files.size() - 1).getPath(); - if (path.equals(prev)) { - continue; - } - } - String relativePath = rootPath.relativize(path.toAbsolutePath()).toString(); - relativePath = toStandardPath(relativePath).replaceAll("[.]+/", ""); - String prefix = classpath ? CLASSPATH_COLON : ""; - files.add(new Resource(path, prefix + relativePath, line, cl)); - } - } - } - - // TODO use this based and tighter routine for feature files above - private static void walkPath(Path root, Set results, Predicate predicate) { - Stream stream; - try { - stream = Files.walk(root); - for (Iterator paths = stream.iterator(); paths.hasNext();) { - Path path = paths.next(); - Path fileName = path.getFileName(); - if (predicate.test(fileName)) { - String relativePath = root.relativize(path.toAbsolutePath()).toString(); - results.add(relativePath); - } - } - } catch (IOException e) { // NoSuchFileException - LOGGER.trace("unable to walk path: {} - {}", root, e.getMessage()); - } - } - - private static final Predicate IS_JS_FILE = p -> p != null && p.toString().endsWith(".js"); - - public static Set jsFiles(File baseDir) { - Set results = new HashSet(); - walkPath(baseDir.toPath().toAbsolutePath(), results, IS_JS_FILE); - return results; - } - - public static Set jsFiles(String basePath) { - Set results = new HashSet(); - try { - Enumeration urls = CLASS_LOADER.getResources(basePath); - while (urls.hasMoreElements()) { - URL url = urls.nextElement(); - Path path = urlToPath(url, null); - walkPath(path, results, IS_JS_FILE); - } - } catch (Exception e) { - LOGGER.warn("unable to scan for js files at: {}", basePath); - } - return results; - } - - public static InputStream resourceAsStream(String resourcePath) { - InputStream is = CLASS_LOADER.getResourceAsStream(resourcePath); - if (is == null) { - throw new RuntimeException("failed to read: " + resourcePath); - } - return is; - } - public static String getBuildDir() { - String temp = System.getProperty("karate.output.dir"); + String temp = System.getProperty(Constants.KARATE_OUTPUT_DIR); if (temp != null) { return temp; } diff --git a/karate-core/src/main/java/com/intuit/karate/Http.java b/karate-core/src/main/java/com/intuit/karate/Http.java index ad6b91756..fe4f2e6cf 100644 --- a/karate-core/src/main/java/com/intuit/karate/Http.java +++ b/karate-core/src/main/java/com/intuit/karate/Http.java @@ -23,10 +23,10 @@ */ package com.intuit.karate; -import com.intuit.karate.core.ScenarioContext; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import com.intuit.karate.core.ScenarioEngine; +import com.intuit.karate.core.Variable; +import com.intuit.karate.http.HttpRequestBuilder; +import com.intuit.karate.http.Response; import java.util.Map; /** @@ -35,123 +35,82 @@ */ public class Http { - private final Match match; public final String urlBase; + private final ScenarioEngine engine; + private final HttpRequestBuilder builder; - public class Response { - - public int status() { - return match.get("responseStatus").asInt(); - } - - public Match body() { - return match.get("response"); - } - - public Match bodyBytes() { - return match.eval("responseBytes"); - } - - public Match jsonPath(String exp) { - return body().jsonPath(exp); - } - - public String header(String name) { - Map map = match.get("responseHeaders").asMap(); - List headers = (List) map.get(name); - if (headers != null && !headers.isEmpty()) { - return headers.get(0); - } - return null; - } + public static Http forUrl(String url) { + return new Http(url); + } + public void setAppender(LogAppender appender) { + engine.logger.setAppender(appender); } - private Http(Match match, String urlBase) { - this.match = match; + private Http(String urlBase) { this.urlBase = urlBase; + engine = ScenarioEngine.forTempUse(); + builder = engine.getRequestBuilder(); + builder.url(urlBase); } public Http url(String url) { - if (url.startsWith("/") && urlBase != null) { - url = urlBase + url; - } - match.context.url(Match.quote(url)); + builder.url(url); return this; } public Http path(String... paths) { - List list = new ArrayList(paths.length); - for (String p : paths) { - list.add(Match.quote(p)); - } - match.context.path(list); + builder.paths(paths); return this; } - + public Http header(String name, String value) { - match.context.header(name, Collections.singletonList(Match.quote(value))); + builder.header(name, value); return this; } - private Response handleError() { - Response res = new Response(); - int code = res.status(); - if (code >= 400) { - match.context.logger.warn("http response code: {}, response: {}, request: {}", - code, res.body().asString(), match.context.getPrevRequest()); + public Response method(String method, Object body) { + if (body != null) { + builder.body(body); } - return res; - } - - public Response get() { - match.context.method("get"); - return handleError(); + builder.method(method); + Response response = engine.httpInvoke(); + if (response.getStatus() >= 400) { + engine.logger.warn("http response code: {}, response: {}, request: {}", + response.getStatus(), response.getBodyAsString(), engine.getRequest()); + } + return response; } - public Response post(String body) { - return post(new Json(body)); - } - - public Response post(byte[] bytes) { - return post(new ScriptValue(bytes)); + public Response method(String method) { + return method(method, null); } - public Response post(Map body) { - return post(new Json(body)); + public Response get() { + return method("get"); } - public Response post(ScriptValue body) { - match.context.request(body); - match.context.method("post"); - return handleError(); + public Response postJson(String body) { + Json json = Json.of(body); + return post(json.value()); } - public Response post(Json json) { // avoid extra eval - return post(json.getValue()); + public Response post(Object body) { + return method("post", body instanceof Json ? ((Json) body).value() : body); } public Response delete() { - match.context.method("delete"); - return handleError(); + return method("delete"); } - public static Http forUrl(LogAppender appender, String url) { - Http http = new Http(Match.forHttp(appender), url); - return http.url(url); - } - - public static Http forUrl(ScenarioContext context, String url) { - Http http = new Http(Match.forHttp(context), url); - return http.url(url); - } - - public Match config(String key, String value) { - return match.config(key, value); + public Http configure(String key, Object value) { + engine.configure(key, new Variable(value)); + return this; } - - public Match config(Map config) { - return match.config(config); + + public Http configure(Map map) { + map.forEach((k, v) -> configure(k, v)); + return this; } } diff --git a/karate-core/src/main/java/com/intuit/karate/Json.java b/karate-core/src/main/java/com/intuit/karate/Json.java index 67dde0d77..18268ea32 100644 --- a/karate-core/src/main/java/com/intuit/karate/Json.java +++ b/karate-core/src/main/java/com/intuit/karate/Json.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright 2018 Intuit Inc. + * Copyright 2020 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 @@ -24,8 +24,14 @@ package com.intuit.karate; import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.PathNotFoundException; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * @@ -33,6 +39,8 @@ */ public class Json { + private static final Logger logger = LoggerFactory.getLogger(Json.class); + private final DocumentContext doc; private final boolean array; private final String prefix; @@ -41,20 +49,34 @@ private String prefix(String path) { return path.charAt(0) == '$' ? path : prefix + path; } - public Json() { - this("{}"); + public static Json object() { + return Json.of("{}"); } - public Json(String json) { - this(JsonUtils.toJsonDoc(json)); + public static Json array() { + return Json.of("[]"); } - public Json(Map map) { - this(JsonUtils.toJsonDoc(map)); + public static Json of(Object o) { + if (o instanceof String) { + return new Json(JsonPath.parse((String) o)); + } else if (o instanceof List) { + return new Json(JsonPath.parse((List) o)); + } else if (o instanceof Map) { + return new Json(JsonPath.parse((Map) o)); + } else { + String json = toJsonString(o); + return new Json(JsonPath.parse(json)); + } } - public Json(Object o) { - this(JsonUtils.toJsonDoc(o)); + private static String toJsonString(Object o) { + try { + return JsonUtils.toJson(o); + } catch (Throwable t) { + logger.warn("object to json serialization failure, trying alternate approach: {}", t.getMessage()); + return JsonUtils.toJsonSafe(o, false); + } } private Json(DocumentContext doc) { @@ -63,69 +85,34 @@ private Json(DocumentContext doc) { prefix = array ? "$" : "$."; } - public DocumentContext getDoc() { - return doc; - } - - public ScriptValue getValue() { - return new ScriptValue(doc); + public Json getJson(String path) { + return Json.of(get(path, String.class)); } - public Json set(String path, Object value) { - JsonUtils.setValueByPath(doc, prefix(path), value); - return this; - } - - public Json set(String path, String value) { - Object temp = value; - String trimmed = StringUtils.trimToNull(value); - if (Script.isJson(trimmed)) { - temp = JsonUtils.toJsonDoc(trimmed).read("$"); - } - set(path, temp); - return this; + public T get(String path) { + return (T) doc.read(prefix(path)); } - public Match getMatcher(String path) { - return Match.init(get(path)); + public T getOptional(String path) { + try { + return get(path); + } catch (Exception e) { + return null; + } } - - public Json getJson(String path) { - return new Json(get(path)); - } - public Object get(String path) { - return doc.read(prefix(path)); + public T getFirst(String path) { + List list = get(path); + if (list == null || list.isEmpty()) { + return null; + } + return list.get(0); } public T get(String path, Class clazz) { return doc.read(prefix(path), clazz); - } - - public String getString(String path) { - return get(path, String.class); - } - - public List getList(String path) { - return get(path, List.class); - } - - public Map getMap(String path) { - return get(path, Map.class); } - public Number getNumber(String path) { - return get(path, Number.class); - } - - public Integer getInteger(String path) { - return get(path, Integer.class); - } - - public Boolean getBoolean(String path) { - return get(path, Boolean.class); - } - @Override public String toString() { return doc.jsonString(); @@ -135,21 +122,152 @@ public boolean isArray() { return array; } - public Object asMapOrList() { + public T value() { return doc.read("$"); - } - + } + + public List asList() { + return value(); + } + public Map asMap() { - return doc.read("$"); + return value(); } - public List> asList() { - return doc.read("$"); + public Json set(String path, String s) { + if (JsonUtils.isJson(s)) { + setInternal(path, Json.of(s).value()); + } else { + if (s != null && s.charAt(0) == '\\') { + s = s.substring(1); + } + setInternal(path, s); + } + return this; } - public Json equals(String exp) { - Match.equals(doc.read("$"), exp); + public Json remove(String path) { + doc.delete(path); return this; } + public Json set(String path, Object o) { + setInternal(path, o); + return this; + } + + private boolean isArrayPath(String s) { + return s.endsWith("]") && !s.endsWith("']"); + } + + private String arrayKey(String s) { + int pos = s.lastIndexOf('['); + return s.substring(0, pos); + } + + private int arrayIndex(String s) { + int leftPos = s.lastIndexOf('['); + if (leftPos == -1) { + return -1; + } + int rightPos = s.indexOf(']', leftPos); + if (leftPos == -1) { + return -1; + } + String num = s.substring(leftPos + 1, rightPos); + if (num.isEmpty()) { + return -1; + } + try { + return Integer.valueOf(num); + } catch (NumberFormatException e) { + return -1; + } + } + + private void setInternal(String path, Object o) { + path = prefix(path); + if ("$".equals(path)) { + throw new RuntimeException("cannot replace root path $"); + } + boolean forArray = isArrayPath(path); + if (!pathExists(path)) { + createPath(path, forArray); + } + StringUtils.Pair pair = toParentAndLeaf(path); + if (forArray) { + int index = arrayIndex(pair.right); + if (index == -1) { + doc.add(arrayKey(path), o); + } else { + doc.set(path, o); + } + } else { + doc.put(pair.left, pair.right, o); + } + } + + public boolean pathExists(String path) { + if (path.endsWith("[]")) { + path = path.substring(0, path.length() - 2); + } + try { + Object temp = doc.read(path); + return temp != null; + } catch (PathNotFoundException pnfe) { + return false; + } + } + + private void createPath(String path, boolean array) { + if (isArrayPath(path)) { + String parentPath = arrayKey(path); + if (!pathExists(parentPath)) { + createPath(parentPath, true); + } + List list = get(parentPath); + if (list == null) { + list = new ArrayList(); + set(parentPath, list); + } + int index = arrayIndex(path); + if (list.size() <= index) { + for (int i = list.size(); i <= index; i++) { + list.add(null); + } + } + } else { + StringUtils.Pair pair = toParentAndLeaf(path); + if (!pathExists(pair.left)) { + createPath(pair.left, false); + } + if (isArrayPath(pair.left)) { + if (isArrayPath(pair.right)) { + doc.set(pair.left, new ArrayList()); + } else { + if (!pathExists(pair.left)) { // a necessary repetition + doc.set(pair.left, new LinkedHashMap()); + } + doc.put(pair.left, pair.right, new LinkedHashMap()); + } + } else { + doc.put(pair.left, pair.right, array ? new ArrayList() : new LinkedHashMap()); + } + } + } + + public static StringUtils.Pair toParentAndLeaf(String path) { + int pos = path.lastIndexOf('.'); + int temp = path.lastIndexOf("['"); + if (temp != -1 && temp > pos) { + pos = temp - 1; + } + String right = path.substring(pos + 1); + if (right.startsWith("[")) { + pos = pos + 1; + } + String left = path.substring(0, pos == -1 ? 0 : pos); + return StringUtils.pair(left, right); + } + } diff --git a/karate-core/src/main/java/com/intuit/karate/JsonUtils.java b/karate-core/src/main/java/com/intuit/karate/JsonUtils.java old mode 100755 new mode 100644 index f6f9e9023..80a1b5499 --- a/karate-core/src/main/java/com/intuit/karate/JsonUtils.java +++ b/karate-core/src/main/java/com/intuit/karate/JsonUtils.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright 2017 Intuit Inc. + * Copyright 2020 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 @@ -23,14 +23,9 @@ */ package com.intuit.karate; -import com.intuit.karate.core.Feature; -import com.intuit.karate.driver.DriverElement; -import com.intuit.karate.driver.Element; +import com.intuit.karate.core.Variable; import com.jayway.jsonpath.Configuration; -import com.jayway.jsonpath.DocumentContext; -import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.Option; -import com.jayway.jsonpath.PathNotFoundException; import com.jayway.jsonpath.spi.json.JsonProvider; import com.jayway.jsonpath.spi.json.JsonSmartJsonProvider; import com.jayway.jsonpath.spi.mapper.JsonSmartMappingProvider; @@ -39,9 +34,9 @@ import de.siegmar.fastcsv.reader.CsvReader; import de.siegmar.fastcsv.reader.CsvRow; import de.siegmar.fastcsv.writer.CsvWriter; -import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; @@ -51,14 +46,11 @@ import java.util.List; import java.util.Map; import java.util.Set; -import jdk.nashorn.api.scripting.ScriptObjectMirror; -import net.minidev.json.JSONArray; import net.minidev.json.JSONStyle; import net.minidev.json.JSONValue; import net.minidev.json.parser.JSONParser; -import net.minidev.json.parser.ParseException; -import net.minidev.json.reader.JsonWriter; -import net.minidev.json.reader.JsonWriterI; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; @@ -68,52 +60,15 @@ */ public class JsonUtils { + private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class); + private JsonUtils() { // only static methods } - private static class NashornObjectJsonWriter implements JsonWriterI { - - @Override - public void writeJSONString(E value, Appendable out, JSONStyle compression) throws IOException { - if (value.isArray()) { - Object[] array = value.values().toArray(); - JsonWriter.arrayWriter.writeJSONString(array, out, compression); - } else if (value.isFunction()) { - JsonWriter.toStringWriter.writeJSONString("\"#function\"", out, compression); - } else { // JSON - JsonWriter.JSONMapWriter.writeJSONString(value, out, compression); - } - } - - } - - private static class FeatureJsonWriter implements JsonWriterI { - - @Override - public void writeJSONString(E value, Appendable out, JSONStyle compression) throws IOException { - JsonWriter.toStringWriter.writeJSONString("\"#feature\"", out, compression); - } - - } - - private static class DriverElementJsonWriter implements JsonWriterI { - - @Override - public void writeJSONString(E value, Appendable out, JSONStyle compression) throws IOException { - JsonWriter.toStringWriter.writeJSONString("\"" + value.getLocator() + "\"", out, compression); - } - - } - static { - // prevent things like the karate script bridge getting serialized (especially in the javafx ui) - JSONValue.registerWriter(ScriptObjectMirror.class, new NashornObjectJsonWriter()); - JSONValue.registerWriter(Feature.class, new FeatureJsonWriter()); - JSONValue.registerWriter(DriverElement.class, new DriverElementJsonWriter()); // ensure that even if jackson (databind?) is on the classpath, don't switch provider Configuration.setDefaults(new Configuration.Defaults() { - private final JsonProvider jsonProvider = new JsonSmartJsonProvider(); private final MappingProvider mappingProvider = new JsonSmartMappingProvider(); @@ -133,70 +88,49 @@ public Set