[TEST] added support for replacing stashed values within objects and lists in our REST tests

This commit is contained in:
javanna 2014-03-03 13:30:21 +01:00 committed by Luca Cavanna
parent 6e955e682b
commit 7d3cd89207
9 changed files with 195 additions and 126 deletions

View File

@ -26,10 +26,6 @@ setup:
---
"Explain API for non-existant node & shard":
- skip:
version: 0-999
reason: param substitution not implemented yet
- do:
cluster.state:
metric: [ master_node ]

View File

@ -19,9 +19,9 @@
package org.elasticsearch.test.rest;
import com.google.common.collect.Maps;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.test.rest.client.RestClient;
import org.elasticsearch.test.rest.client.RestException;
import org.elasticsearch.test.rest.client.RestResponse;
@ -31,6 +31,7 @@ import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@ -47,7 +48,7 @@ public class RestTestExecutionContext implements Closeable {
private final String esVersion;
private final Map<String, Object> stash = Maps.newHashMap();
private final Stash stash = new Stash();
private RestResponse response;
@ -61,18 +62,21 @@ public class RestTestExecutionContext implements Closeable {
* Saves the obtained response in the execution context.
* @throws RestException if the returned status code is non ok
*/
public RestResponse callApi(String apiName, Map<String, String> params, String body) throws IOException, RestException {
public RestResponse callApi(String apiName, Map<String, String> params, List<Map<String, Object>> bodies) throws IOException, RestException {
//makes a copy of the parameters before modifying them for this specific request
HashMap<String, String> requestParams = Maps.newHashMap(params);
for (Map.Entry<String, String> entry : requestParams.entrySet()) {
if (isStashed(entry.getValue())) {
entry.setValue(unstash(entry.getValue()).toString());
if (stash.isStashedValue(entry.getValue())) {
entry.setValue(stash.unstashValue(entry.getValue()).toString());
}
}
String body = actualBody(bodies);
try {
response = callApiInternal(apiName, requestParams, body);
//we always stash the last response body
stash("body", response.getBody());
stash.stashValue("body", response.getBody());
return response;
} catch(RestException e) {
response = e.restResponse();
@ -80,6 +84,26 @@ public class RestTestExecutionContext implements Closeable {
}
}
private String actualBody(List<Map<String, Object>> bodies) throws IOException {
if (bodies.isEmpty()) {
return "";
}
if (bodies.size() == 1) {
return bodyAsString(stash.unstashMap(bodies.get(0)));
}
StringBuilder bodyBuilder = new StringBuilder();
for (Map<String, Object> body : bodies) {
bodyBuilder.append(bodyAsString(stash.unstashMap(body))).append("\n");
}
return bodyBuilder.toString();
}
private String bodyAsString(Map<String, Object> body) throws IOException {
return XContentFactory.jsonBuilder().map(body).string();
}
/**
* Calls an elasticsearch api internally without saving the obtained response in the context.
* Useful for internal calls (e.g. delete index during teardown)
@ -109,41 +133,8 @@ public class RestTestExecutionContext implements Closeable {
stash.clear();
}
/**
* Tells whether a particular value needs to be looked up in the stash
* The stash contains fields eventually extracted from previous responses that can be reused
* as arguments for following requests (e.g. scroll_id)
*/
public boolean isStashed(Object key) {
if (key == null) {
return false;
}
String stashKey = key.toString();
return Strings.hasLength(stashKey) && stashKey.startsWith("$");
}
/**
* Extracts a value from the current stash
* The stash contains fields eventually extracted from previous responses that can be reused
* as arguments for following requests (e.g. scroll_id)
*/
public Object unstash(String value) {
Object stashedValue = stash.get(value.substring(1));
if (stashedValue == null) {
throw new IllegalArgumentException("stashed value not found for key [" + value + "]");
}
return stashedValue;
}
/**
* Allows to saved a specific field in the stash as key-value pair
*/
public void stash(String key, Object value) {
logger.debug("stashing [{}]=[{}]", key, value);
Object old = stash.put(key, value);
if (old != null && old != value) {
logger.trace("replaced stashed value [{}] with same key [{}]", old, key);
}
public Stash stash() {
return stash;
}
/**

View File

@ -0,0 +1,117 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.test.rest;
import com.google.common.collect.Maps;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import java.util.List;
import java.util.Map;
/**
* Allows to cache the last obtained test response and or part of it within variables
* that can be used as input values in following requests and assertions.
*/
public class Stash {
private static final ESLogger logger = Loggers.getLogger(Stash.class);
private final Map<String, Object> stash = Maps.newHashMap();
/**
* Allows to saved a specific field in the stash as key-value pair
*/
public void stashValue(String key, Object value) {
logger.debug("stashing [{}]=[{}]", key, value);
Object old = stash.put(key, value);
if (old != null && old != value) {
logger.trace("replaced stashed value [{}] with same key [{}]", old, key);
}
}
/**
* Clears the previously stashed values
*/
public void clear() {
stash.clear();
}
/**
* Tells whether a particular value needs to be looked up in the stash
* The stash contains fields eventually extracted from previous responses that can be reused
* as arguments for following requests (e.g. scroll_id)
*/
public boolean isStashedValue(Object key) {
if (key == null) {
return false;
}
String stashKey = key.toString();
return Strings.hasLength(stashKey) && stashKey.startsWith("$");
}
/**
* Extracts a value from the current stash
* The stash contains fields eventually extracted from previous responses that can be reused
* as arguments for following requests (e.g. scroll_id)
*/
public Object unstashValue(String value) {
Object stashedValue = stash.get(value.substring(1));
if (stashedValue == null) {
throw new IllegalArgumentException("stashed value not found for key [" + value + "]");
}
return stashedValue;
}
/**
* Recursively unstashes map values if needed
*/
public Map<String, Object> unstashMap(Map<String, Object> map) {
Map<String, Object> copy = Maps.newHashMap(map);
unstashObject(copy);
return copy;
}
@SuppressWarnings("unchecked")
private void unstashObject(Object obj) {
if (obj instanceof List) {
List list = (List)obj;
for (int i = 0; i < list.size(); i++) {
Object o = list.get(i);
if (isStashedValue(o)) {
list.set(i, unstashValue(o.toString()));
} else {
unstashObject(o);
}
}
}
if (obj instanceof Map) {
Map<String, Object> map = (Map) obj;
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (isStashedValue(entry.getValue())) {
entry.setValue(unstashValue(entry.getValue().toString()));
} else {
unstashObject(entry.getValue());
}
}
}
}
}

View File

@ -18,14 +18,13 @@
*/
package org.elasticsearch.test.rest.parser;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.rest.section.ApiCallSection;
import org.elasticsearch.test.rest.section.DoSection;
import java.io.IOException;
import java.util.Map;
/**
* Parser for do sections
@ -58,15 +57,19 @@ public class DoSectionParser implements RestTestFragmentParser<DoSection> {
paramName = parser.currentName();
} else if (token.isValue()) {
if ("body".equals(paramName)) {
apiCallSection.addBody(parser.text());
String body = parser.text();
XContentType bodyContentType = XContentFactory.xContentType(body);
XContentParser bodyParser = XContentFactory.xContent(bodyContentType).createParser(body);
//multiple bodies are supported e.g. in case of bulk provided as a whole string
while(bodyParser.nextToken() != null) {
apiCallSection.addBody(bodyParser.mapOrdered());
}
} else {
apiCallSection.addParam(paramName, parser.text());
}
} else if (token == XContentParser.Token.START_OBJECT) {
if ("body".equals(paramName)) {
Map<String,Object> map = parser.mapOrdered();
XContentBuilder contentBuilder = XContentFactory.jsonBuilder().map(map);
apiCallSection.addBody(contentBuilder.string());
apiCallSection.addBody(parser.mapOrdered());
}
}
}

View File

@ -34,9 +34,7 @@ public class ApiCallSection {
private final String api;
private final Map<String, String> params = Maps.newHashMap();
private final List<String> bodies = Lists.newArrayList();
private static final String EMPTY_BODY = "";
private final List<Map<String, Object>> bodies = Lists.newArrayList();
public ApiCallSection(String api) {
this.api = api;
@ -59,27 +57,11 @@ public class ApiCallSection {
this.params.put(key, value);
}
public List<String> getBodiesAsList() {
public List<Map<String, Object>> getBodies() {
return ImmutableList.copyOf(bodies);
}
public String getBody() {
if (bodies.size() == 0) {
return EMPTY_BODY;
}
if (bodies.size() == 1) {
return bodies.get(0);
}
StringBuilder bodyBuilder = new StringBuilder();
for (String body : bodies) {
bodyBuilder.append(body).append("\n");
}
return bodyBuilder.toString();
}
public void addBody(String body) {
public void addBody(Map<String, Object> body) {
this.bodies.add(body);
}

View File

@ -21,6 +21,7 @@ package org.elasticsearch.test.rest.section;
import org.elasticsearch.test.rest.RestTestExecutionContext;
import java.io.IOException;
import java.util.Map;
/**
* Base class for executable sections that hold assertions
@ -43,16 +44,22 @@ public abstract class Assertion implements ExecutableSection {
return expectedValue;
}
protected final Object resolveExpectedValue(RestTestExecutionContext executionContext) {
if (executionContext.isStashed(expectedValue)) {
return executionContext.unstash(expectedValue.toString());
protected final Object resolveExpectedValue(RestTestExecutionContext executionContext) throws IOException {
if (expectedValue instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) expectedValue;
return executionContext.stash().unstashMap(map);
}
if (executionContext.stash().isStashedValue(expectedValue)) {
return executionContext.stash().unstashValue(expectedValue.toString());
}
return expectedValue;
}
protected final Object getActualValue(RestTestExecutionContext executionContext) throws IOException {
if (executionContext.isStashed(field)) {
return executionContext.unstash(field);
if (executionContext.stash().isStashedValue(field)) {
return executionContext.stash().unstashValue(field);
}
return executionContext.response(field);
}

View File

@ -82,7 +82,7 @@ public class DoSection implements ExecutableSection {
}
try {
RestResponse restResponse = executionContext.callApi(apiCallSection.getApi(), apiCallSection.getParams(), apiCallSection.getBody());
RestResponse restResponse = executionContext.callApi(apiCallSection.getApi(), apiCallSection.getParams(), apiCallSection.getBodies());
if (Strings.hasLength(catchParam)) {
String catchStatusCode;
if (catches.containsKey(catchParam)) {

View File

@ -46,7 +46,7 @@ public class SetSection implements ExecutableSection {
public void execute(RestTestExecutionContext executionContext) throws IOException {
for (Map.Entry<String, String> entry : stash.entrySet()) {
Object actualValue = executionContext.response(entry.getKey());
executionContext.stash(entry.getValue(), actualValue);
executionContext.stash().stashValue(entry.getValue(), actualValue);
}
}
}

View File

@ -98,8 +98,7 @@ public class DoSectionParserTests extends AbstractParserTests {
assertThat(apiCallSection.getParams().get("id"), equalTo("1"));
assertThat(apiCallSection.hasBody(), equalTo(true));
assertJsonEquals(apiCallSection.getBodiesAsList().get(0), body);
assertJsonEquals(apiCallSection.getBody(), body);
assertJsonEquals(apiCallSection.getBodies().get(0), body);
}
@Test
@ -129,12 +128,7 @@ public class DoSectionParserTests extends AbstractParserTests {
assertThat(apiCallSection.getParams().size(), equalTo(1));
assertThat(apiCallSection.getParams().get("refresh"), equalTo("true"));
assertThat(apiCallSection.hasBody(), equalTo(true));
assertThat(apiCallSection.getBodiesAsList().size(), equalTo(1));
StringBuilder bodyBuilder = new StringBuilder();
for (String body : bodies) {
bodyBuilder.append(body);
}
assertThat(apiCallSection.getBody(), equalTo(bodyBuilder.toString()));
assertThat(apiCallSection.getBodies().size(), equalTo(4));
}
@Test
@ -161,15 +155,9 @@ public class DoSectionParserTests extends AbstractParserTests {
assertThat(apiCallSection.getParams().size(), equalTo(1));
assertThat(apiCallSection.getParams().get("refresh"), equalTo("true"));
assertThat(apiCallSection.hasBody(), equalTo(true));
assertThat(apiCallSection.getBodiesAsList().size(), equalTo(bodies.length));
assertThat(apiCallSection.getBodies().size(), equalTo(bodies.length));
for (int i = 0; i < bodies.length; i++) {
assertJsonEquals(apiCallSection.getBodiesAsList().get(i), bodies[i]);
}
String[] returnedBodies = apiCallSection.getBody().split("\n");
assertThat(returnedBodies.length, equalTo(bodies.length));
for (int i = 0; i < bodies.length; i++) {
assertJsonEquals(returnedBodies[i], bodies[i]);
assertJsonEquals(apiCallSection.getBodies().get(i), bodies[i]);
}
}
@ -191,9 +179,8 @@ public class DoSectionParserTests extends AbstractParserTests {
assertThat(apiCallSection.getApi(), equalTo("search"));
assertThat(apiCallSection.getParams().size(), equalTo(0));
assertThat(apiCallSection.hasBody(), equalTo(true));
assertThat(apiCallSection.getBodiesAsList().size(), equalTo(1));
assertJsonEquals(apiCallSection.getBodiesAsList().get(0), body);
assertJsonEquals(apiCallSection.getBody(), body);
assertThat(apiCallSection.getBodies().size(), equalTo(1));
assertJsonEquals(apiCallSection.getBodies().get(0), body);
}
@Test
@ -230,16 +217,10 @@ public class DoSectionParserTests extends AbstractParserTests {
assertThat(apiCallSection.getParams().size(), equalTo(1));
assertThat(apiCallSection.getParams().get("refresh"), equalTo("true"));
assertThat(apiCallSection.hasBody(), equalTo(true));
assertThat(apiCallSection.getBodiesAsList().size(), equalTo(bodies.length));
assertThat(apiCallSection.getBodies().size(), equalTo(bodies.length));
for (int i = 0; i < bodies.length; i++) {
assertJsonEquals(apiCallSection.getBodiesAsList().get(i), bodies[i]);
}
String[] returnedBodies = apiCallSection.getBody().split("\n");
assertThat(returnedBodies.length, equalTo(bodies.length));
for (int i = 0; i < bodies.length; i++) {
assertJsonEquals(returnedBodies[i], bodies[i]);
assertJsonEquals(apiCallSection.getBodies().get(i), bodies[i]);
}
}
@ -270,16 +251,10 @@ public class DoSectionParserTests extends AbstractParserTests {
assertThat(apiCallSection.getParams().size(), equalTo(1));
assertThat(apiCallSection.getParams().get("refresh"), equalTo("true"));
assertThat(apiCallSection.hasBody(), equalTo(true));
assertThat(apiCallSection.getBodiesAsList().size(), equalTo(bodies.length));
assertThat(apiCallSection.getBodies().size(), equalTo(bodies.length));
for (int i = 0; i < bodies.length; i++) {
assertJsonEquals(apiCallSection.getBodiesAsList().get(i), bodies[i]);
}
String[] returnedBodies = apiCallSection.getBody().split("\n");
assertThat(returnedBodies.length, equalTo(bodies.length));
for (int i = 0; i < bodies.length; i++) {
assertJsonEquals(returnedBodies[i], bodies[i]);
assertJsonEquals(apiCallSection.getBodies().get(i), bodies[i]);
}
}
@ -305,9 +280,8 @@ public class DoSectionParserTests extends AbstractParserTests {
assertThat(apiCallSection.getApi(), equalTo("mget"));
assertThat(apiCallSection.getParams().size(), equalTo(0));
assertThat(apiCallSection.hasBody(), equalTo(true));
assertThat(apiCallSection.getBodiesAsList().size(), equalTo(1));
assertJsonEquals(apiCallSection.getBodiesAsList().get(0), body);
assertJsonEquals(apiCallSection.getBody(), body);
assertThat(apiCallSection.getBodies().size(), equalTo(1));
assertJsonEquals(apiCallSection.getBodies().get(0), body);
}
@Test
@ -331,9 +305,9 @@ public class DoSectionParserTests extends AbstractParserTests {
assertThat(apiCallSection.getParams().get("type"), equalTo("test"));
assertThat(apiCallSection.getParams().get("id"), equalTo("1"));
assertThat(apiCallSection.hasBody(), equalTo(true));
assertThat(apiCallSection.getBodiesAsList().size(), equalTo(1));
assertThat(apiCallSection.getBodies().size(), equalTo(1));
//stringified body is taken as is
assertThat(apiCallSection.getBodiesAsList().get(0), equalTo("{ _source: true, query: { match_all: {} } }"));
assertJsonEquals(apiCallSection.getBodies().get(0), "{ _source: true, query: { match_all: {} } }");
}
@Test
@ -354,10 +328,10 @@ public class DoSectionParserTests extends AbstractParserTests {
assertThat(apiCallSection.getApi(), equalTo("index"));
assertThat(apiCallSection.getParams().size(), equalTo(0));
assertThat(apiCallSection.hasBody(), equalTo(true));
assertThat(apiCallSection.getBodiesAsList().size(), equalTo(2));
assertThat(apiCallSection.getBodies().size(), equalTo(2));
//stringified body is taken as is
assertThat(apiCallSection.getBodiesAsList().get(0), equalTo("{ _source: true, query: { match_all: {} } }"));
assertJsonEquals(apiCallSection.getBodiesAsList().get(1), body);
assertJsonEquals(apiCallSection.getBodies().get(0), "{ _source: true, query: { match_all: {} } }");
assertJsonEquals(apiCallSection.getBodies().get(1), body);
}
@Test
@ -409,12 +383,11 @@ public class DoSectionParserTests extends AbstractParserTests {
assertThat(doSection.getApiCallSection().getParams().get("type"), equalTo("test_type"));
assertThat(doSection.getApiCallSection().getParams().get("field"), equalTo("text,text1"));
assertThat(doSection.getApiCallSection().hasBody(), equalTo(false));
assertThat(doSection.getApiCallSection().getBodiesAsList().size(), equalTo(0));
assertThat(doSection.getApiCallSection().getBodies().size(), equalTo(0));
}
private static void assertJsonEquals(String actual, String expected) throws IOException {
Map<String,Object> actualMap = JsonXContent.jsonXContent.createParser(actual).mapOrderedAndClose();
private static void assertJsonEquals(Map<String, Object> actual, String expected) throws IOException {
Map<String,Object> expectedMap = JsonXContent.jsonXContent.createParser(expected).mapOrderedAndClose();
MatcherAssert.assertThat(actualMap, equalTo(expectedMap));
MatcherAssert.assertThat(actual, equalTo(expectedMap));
}
}