[TEST] Added params validation according to REST spec and check whether body is supported or required

This commit is contained in:
Luca Cavanna 2014-01-16 21:09:45 +01:00
parent 92a5d6a8af
commit 81846151f7
6 changed files with 161 additions and 58 deletions

View File

@ -158,7 +158,18 @@ public class RestClient implements Closeable {
private HttpRequestBuilder callApiBuilder(String apiName, Map<String, String> params, String body) {
RestApi restApi = restApi(apiName);
HttpRequestBuilder httpRequestBuilder = httpRequestBuilder().body(body);
HttpRequestBuilder httpRequestBuilder = httpRequestBuilder();
if (Strings.hasLength(body)) {
if (!restApi.isBodySupported()) {
throw new IllegalArgumentException("body is not supported by [" + restApi.getName() + "] api");
}
httpRequestBuilder.body(body);
} else {
if (restApi.isBodyRequired()) {
throw new IllegalArgumentException("body is required by [" + restApi.getName() + "] api");
}
}
//divide params between ones that go within query string and ones that go within path
Map<String, String> pathParts = Maps.newHashMap();
@ -167,6 +178,9 @@ public class RestClient implements Closeable {
if (restApi.getPathParts().contains(entry.getKey())) {
pathParts.put(entry.getKey(), entry.getValue());
} else {
if (!restApi.getParams().contains(entry.getKey())) {
throw new IllegalArgumentException("param [" + entry.getKey() + "] not supported in [" + restApi.getName() + "] api");
}
httpRequestBuilder.addParam(entry.getKey(), entry.getValue());
}
}

View File

@ -539,8 +539,7 @@ public class RestTestSuiteRunner extends ParentRunner<RestTestCandidate> {
logger.debug("deleting all templates");
//delete templates by wildcard was only added in 0.90.6
//httpResponse = restTestExecutionContext.callApi("indices.delete_template", "name", "*");
RestResponse restResponse = restTestExecutionContext.callApiInternal("cluster.state", "filter_nodes", "true",
"filter_routing_table", "true", "filter_blocks", "true");
RestResponse restResponse = restTestExecutionContext.callApiInternal("cluster.state", "metric", "metadata");
assertThat(restResponse.getStatusCode(), equalTo(200));
Object object = restResponse.evaluate("metadata.templates");
assertThat(object, instanceOf(Map.class));

View File

@ -39,23 +39,32 @@ public class RestApi {
private List<String> methods = Lists.newArrayList();
private List<String> paths = Lists.newArrayList();
private List<String> pathParts = Lists.newArrayList();
private List<String> params = Lists.newArrayList();
private BODY body = BODY.NOT_SUPPORTED;
public static enum BODY {
NOT_SUPPORTED, OPTIONAL, REQUIRED
}
RestApi(String name) {
this.name = name;
}
RestApi(RestApi restApi, String name, String... methods) {
this.name = name;
this.methods = Arrays.asList(methods);
paths.addAll(restApi.getPaths());
pathParts.addAll(restApi.getPathParts());
this(restApi, name, restApi.getPaths(), Arrays.asList(methods));
}
RestApi(RestApi restApi, List<String> paths) {
this.name = restApi.getName();
this.methods = restApi.getMethods();
this(restApi, restApi.getName(), paths, restApi.getMethods());
}
private RestApi(RestApi restApi, String name, List<String> paths, List<String> methods) {
this.name = name;
this.paths.addAll(paths);
pathParts.addAll(restApi.getPathParts());
this.methods.addAll(methods);
this.pathParts.addAll(restApi.getPathParts());
this.params.addAll(restApi.getParams());
this.body = restApi.body;
}
public String getName() {
@ -112,6 +121,30 @@ public class RestApi {
this.pathParts.add(pathPart);
}
public List<String> getParams() {
return params;
}
void addParam(String param) {
this.params.add(param);
}
void setBodyOptional() {
this.body = BODY.OPTIONAL;
}
void setBodyRequired() {
this.body = BODY.REQUIRED;
}
public boolean isBodySupported() {
return body != BODY.NOT_SUPPORTED;
}
public boolean isBodyRequired() {
return body == BODY.REQUIRED;
}
/**
* Finds the best matching rest path given the current parameters and replaces
* placeholders with their corresponding values received as arguments
@ -120,14 +153,14 @@ public class RestApi {
List<RestPath> matchingRestPaths = findMatchingRestPaths(pathParams.keySet());
if (matchingRestPaths == null || matchingRestPaths.isEmpty()) {
throw new IllegalArgumentException("unable to find matching rest path for api [" + name + "] and params " + pathParams);
throw new IllegalArgumentException("unable to find matching rest path for api [" + name + "] and path params " + pathParams);
}
String[] paths = new String[matchingRestPaths.size()];
for (int i = 0; i < matchingRestPaths.size(); i++) {
RestPath restPath = matchingRestPaths.get(i);
String path = restPath.path;
for (Map.Entry<String, String> paramEntry : restPath.params.entrySet()) {
for (Map.Entry<String, String> paramEntry : restPath.parts.entrySet()) {
// replace path placeholders with actual values
String value = pathParams.get(paramEntry.getValue());
if (value == null) {
@ -144,7 +177,7 @@ public class RestApi {
* Finds the matching rest paths out of the available ones with the current api (based on REST spec).
*
* The best path is the one that has exactly the same number of placeholders to replace
* (e.g. /{index}/{type}/{id} when the params are exactly index, type and id).
* (e.g. /{index}/{type}/{id} when the path params are exactly index, type and id).
*/
private List<RestPath> findMatchingRestPaths(Set<String> restParams) {
@ -152,8 +185,8 @@ public class RestApi {
RestPath[] restPaths = buildRestPaths();
for (RestPath restPath : restPaths) {
if (restPath.params.size() == restParams.size()) {
if (restPath.params.values().containsAll(restParams)) {
if (restPath.parts.size() == restParams.size()) {
if (restPath.parts.values().containsAll(restParams)) {
matchingRestPaths.add(restPath);
}
}
@ -175,15 +208,15 @@ public class RestApi {
final String path;
//contains param to replace (e.g. {index}) and param key to use for lookup in the current values map (e.g. index)
final Map<String, String> params;
final Map<String, String> parts;
RestPath(String path) {
this.path = path;
this.params = extractParams(path);
this.parts = extractParts(path);
}
private static Map<String,String> extractParams(String input) {
Map<String, String> params = Maps.newHashMap();
private static Map<String,String> extractParts(String input) {
Map<String, String> parts = Maps.newHashMap();
Matcher matcher = PLACEHOLDERS_PATTERN.matcher(input);
while (matcher.find()) {
//key is e.g. {index}
@ -193,9 +226,9 @@ public class RestApi {
}
//to be replaced with current value found with key e.g. index
String value = matcher.group(2);
params.put(key, value);
parts.put(key, value);
}
return params;
return parts;
}
}
}

View File

@ -70,6 +70,15 @@ public class RestApiParser {
}
}
if (parser.currentToken() == XContentParser.Token.START_OBJECT && "params".equals(currentFieldName)) {
while (parser.nextToken() == XContentParser.Token.FIELD_NAME) {
restApi.addParam(parser.currentName());
parser.nextToken();
assert parser.currentToken() == XContentParser.Token.START_OBJECT;
parser.skipChildren();
}
}
if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
innerLevel++;
}
@ -78,6 +87,29 @@ public class RestApiParser {
}
}
}
if ("body".equals(parser.currentName())) {
parser.nextToken();
if (parser.currentToken() != XContentParser.Token.VALUE_NULL) {
boolean requiredFound = false;
while(parser.nextToken() != XContentParser.Token.END_OBJECT) {
if (parser.currentToken() == XContentParser.Token.FIELD_NAME) {
if ("required".equals(parser.currentName())) {
requiredFound = true;
parser.nextToken();
if (parser.booleanValue()) {
restApi.setBodyRequired();
} else {
restApi.setBodyOptional();
}
}
}
}
if (!requiredFound) {
restApi.setBodyOptional();
}
}
}
}
if (parser.currentToken() == XContentParser.Token.START_OBJECT) {

View File

@ -41,12 +41,12 @@ public class RestSpec {
void addApi(RestApi restApi) {
if ("info".equals(restApi.getName())) {
//info and ping should really be two different api in the rest spec
//TODO info and ping should really be two different api in the rest spec
//info (GET|HEAD /) needs to be manually split into 1) info: GET / 2) ping: HEAD /
restApiMap.put("info", new RestApi(restApi, "info", "GET"));
restApiMap.put("ping", new RestApi(restApi, "ping", "HEAD"));
} else if ("get".equals(restApi.getName())) {
//get_source endpoint shouldn't be present in the rest spec for the get api
//TODO get_source endpoint shouldn't be present in the rest spec for the get api
//as get_source is already a separate api
List<String> paths = Lists.newArrayList();
for (String path : restApi.getPaths()) {

View File

@ -23,6 +23,7 @@ import org.elasticsearch.test.rest.spec.RestApi;
import org.elasticsearch.test.rest.spec.RestApiParser;
import org.junit.Test;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
@ -45,6 +46,10 @@ public class RestApiParserTests extends AbstractParserTests {
assertThat(restApi.getPathParts().get(0), equalTo("id"));
assertThat(restApi.getPathParts().get(1), equalTo("index"));
assertThat(restApi.getPathParts().get(2), equalTo("type"));
assertThat(restApi.getParams().size(), equalTo(4));
assertThat(restApi.getParams(), contains("consistency", "op_type", "parent", "refresh"));
assertThat(restApi.isBodySupported(), equalTo(true));
assertThat(restApi.isBodyRequired(), equalTo(true));
}
@Test
@ -60,8 +65,63 @@ public class RestApiParserTests extends AbstractParserTests {
assertThat(restApi.getPaths().get(1), equalTo("/_template/{name}"));
assertThat(restApi.getPathParts().size(), equalTo(1));
assertThat(restApi.getPathParts().get(0), equalTo("name"));
assertThat(restApi.getParams().size(), equalTo(0));
assertThat(restApi.isBodySupported(), equalTo(false));
assertThat(restApi.isBodyRequired(), equalTo(false));
}
@Test
public void testParseRestSpecCountApi() throws Exception {
parser = JsonXContent.jsonXContent.createParser(REST_SPEC_COUNT_API);
RestApi restApi = new RestApiParser().parse(parser);
assertThat(restApi, notNullValue());
assertThat(restApi.getName(), equalTo("count"));
assertThat(restApi.getMethods().size(), equalTo(2));
assertThat(restApi.getMethods().get(0), equalTo("POST"));
assertThat(restApi.getMethods().get(1), equalTo("GET"));
assertThat(restApi.getPaths().size(), equalTo(3));
assertThat(restApi.getPaths().get(0), equalTo("/_count"));
assertThat(restApi.getPaths().get(1), equalTo("/{index}/_count"));
assertThat(restApi.getPaths().get(2), equalTo("/{index}/{type}/_count"));
assertThat(restApi.getPathParts().size(), equalTo(2));
assertThat(restApi.getPathParts().get(0), equalTo("index"));
assertThat(restApi.getPathParts().get(1), equalTo("type"));
assertThat(restApi.getParams().size(), equalTo(1));
assertThat(restApi.getParams().get(0), equalTo("ignore_unavailable"));
assertThat(restApi.isBodySupported(), equalTo(true));
assertThat(restApi.isBodyRequired(), equalTo(false));
}
private static final String REST_SPEC_COUNT_API = "{\n" +
" \"count\": {\n" +
" \"documentation\": \"http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-count.html\",\n" +
" \"methods\": [\"POST\", \"GET\"],\n" +
" \"url\": {\n" +
" \"path\": \"/_count\",\n" +
" \"paths\": [\"/_count\", \"/{index}/_count\", \"/{index}/{type}/_count\"],\n" +
" \"parts\": {\n" +
" \"index\": {\n" +
" \"type\" : \"list\",\n" +
" \"description\" : \"A comma-separated list of indices to restrict the results\"\n" +
" },\n" +
" \"type\": {\n" +
" \"type\" : \"list\",\n" +
" \"description\" : \"A comma-separated list of types to restrict the results\"\n" +
" }\n" +
" },\n" +
" \"params\": {\n" +
" \"ignore_unavailable\": {\n" +
" \"type\" : \"boolean\",\n" +
" \"description\" : \"Whether specified concrete indices should be ignored when unavailable (missing or closed)\"\n" +
" } \n" +
" }\n" +
" },\n" +
" \"body\": {\n" +
" \"description\" : \"A query to restrict the results specified with the Query DSL (optional)\"\n" +
" }\n" +
" }\n" +
"}\n";
private static final String REST_SPEC_GET_TEMPLATE_API = "{\n" +
" \"indices.get_template\": {\n" +
" \"documentation\": \"http://www.elasticsearch.org/guide/reference/api/admin-indices-templates/\",\n" +
@ -122,44 +182,9 @@ public class RestApiParserTests extends AbstractParserTests {
" \"type\" : \"string\",\n" +
" \"description\" : \"ID of the parent document\"\n" +
" },\n" +
" \"percolate\": {\n" +
" \"type\" : \"string\",\n" +
" \"description\" : \"Percolator queries to execute while indexing the document\"\n" +
" },\n" +
" \"refresh\": {\n" +
" \"type\" : \"boolean\",\n" +
" \"description\" : \"Refresh the index after performing the operation\"\n" +
" },\n" +
" \"replication\": {\n" +
" \"type\" : \"enum\",\n" +
" \"options\" : [\"sync\",\"async\"],\n" +
" \"default\" : \"sync\",\n" +
" \"description\" : \"Specific replication type\"\n" +
" },\n" +
" \"routing\": {\n" +
" \"type\" : \"string\",\n" +
" \"description\" : \"Specific routing value\"\n" +
" },\n" +
" \"timeout\": {\n" +
" \"type\" : \"time\",\n" +
" \"description\" : \"Explicit operation timeout\"\n" +
" },\n" +
" \"timestamp\": {\n" +
" \"type\" : \"time\",\n" +
" \"description\" : \"Explicit timestamp for the document\"\n" +
" },\n" +
" \"ttl\": {\n" +
" \"type\" : \"duration\",\n" +
" \"description\" : \"Expiration time for the document\"\n" +
" },\n" +
" \"version\" : {\n" +
" \"type\" : \"number\",\n" +
" \"description\" : \"Explicit version number for concurrency control\"\n" +
" },\n" +
" \"version_type\": {\n" +
" \"type\" : \"enum\",\n" +
" \"options\" : [\"internal\",\"external\"],\n" +
" \"description\" : \"Specific version type\"\n" +
" }\n" +
" }\n" +
" },\n" +