Fail yaml tests and docs snippets that get unexpected warnings

Adds `warnings` syntax to the yaml test that allows you to expect
a `Warning` header that looks like:
```
    - do:
        warnings:
            - '[index] is deprecated'
            - quotes are not required because yaml
            - but this argument is always a list, never a single string
            - no matter how many warnings you expect
        get:
            index:    test
            type:    test
            id:        1
```

These are accessible from the docs with:
```
// TEST[warning:some warning]
```

This should help to force you to update the docs if you deprecate
something. You *must* add the warnings marker to the docs or the build
will fail. While you are there you *should* update the docs to add
deprecation warnings visible in the rendered results.
This commit is contained in:
Nik Everett 2016-08-02 17:35:31 -04:00
parent 4598c36027
commit 1e587406d8
26 changed files with 347 additions and 57 deletions

View File

@ -119,6 +119,7 @@ public class RestTestsFromSnippetsTask extends SnippetsTask {
current.println(" reason: $test.skipTest")
}
if (test.setup != null) {
// Insert a setup defined outside of the docs
String setup = setups[test.setup]
if (setup == null) {
throw new InvalidUserDataException("Couldn't find setup "
@ -136,13 +137,23 @@ public class RestTestsFromSnippetsTask extends SnippetsTask {
response.contents.eachLine { current.println(" $it") }
}
void emitDo(String method, String pathAndQuery,
String body, String catchPart, boolean inSetup) {
void emitDo(String method, String pathAndQuery, String body,
String catchPart, List warnings, boolean inSetup) {
def (String path, String query) = pathAndQuery.tokenize('?')
current.println(" - do:")
if (catchPart != null) {
current.println(" catch: $catchPart")
}
if (false == warnings.isEmpty()) {
current.println(" warnings:")
for (String warning in warnings) {
// Escape " because we're going to quote the warning
String escaped = warning.replaceAll('"', '\\\\"')
/* Quote the warning in case it starts with [ which makes
* it look too much like an array. */
current.println(" - \"$escaped\"")
}
}
current.println(" raw:")
current.println(" method: $method")
current.println(" path: \"$path\"")
@ -200,7 +211,8 @@ public class RestTestsFromSnippetsTask extends SnippetsTask {
// Leading '/'s break the generated paths
pathAndQuery = pathAndQuery.substring(1)
}
emitDo(method, pathAndQuery, body, catchPart, inSetup)
emitDo(method, pathAndQuery, body, catchPart, snippet.warnings,
inSetup)
}
}

View File

@ -37,8 +37,9 @@ public class SnippetsTask extends DefaultTask {
private static final String CATCH = /catch:\s*((?:\/[^\/]+\/)|[^ \]]+)/
private static final String SKIP = /skip:([^\]]+)/
private static final String SETUP = /setup:([^ \]]+)/
private static final String WARNING = /warning:(.+)/
private static final String TEST_SYNTAX =
/(?:$CATCH|$SUBSTITUTION|$SKIP|(continued)|$SETUP) ?/
/(?:$CATCH|$SUBSTITUTION|$SKIP|(continued)|$SETUP|$WARNING) ?/
/**
* Action to take on each snippet. Called with a single parameter, an
@ -158,6 +159,10 @@ public class SnippetsTask extends DefaultTask {
snippet.setup = it.group(6)
return
}
if (it.group(7) != null) {
snippet.warnings.add(it.group(7))
return
}
throw new InvalidUserDataException(
"Invalid test marker: $line")
}
@ -230,6 +235,7 @@ public class SnippetsTask extends DefaultTask {
String language = null
String catchPart = null
String setup = null
List warnings = new ArrayList()
@Override
public String toString() {
@ -254,6 +260,9 @@ public class SnippetsTask extends DefaultTask {
if (setup) {
result += "[setup:$setup]"
}
for (String warning in warnings) {
result += "[warning:$warning]"
}
}
if (testResponse) {
result += '// TESTRESPONSE'

View File

@ -28,6 +28,10 @@ are tests even if they don't have `// CONSOLE`.
* `// TEST[setup:name]`: Run some setup code before running the snippet. This
is useful for creating and populating indexes used in the snippet. The setup
code is defined in `docs/build.gradle`.
* `// TEST[warning:some warning]`: Expect the response to include a `Warning`
header. If the response doesn't include a `Warning` header with the exact
text then the test fails. If the response includes `Warning` headers that
aren't expected then the test fails.
* `// TESTRESPONSE`: Matches this snippet against the body of the response of
the last test. If the response is JSON then order is ignored. With
`// TEST[continued]` you can make tests that contain multiple command snippets

View File

@ -196,14 +196,14 @@ PUT /test
"file" : {
"type" : "attachment",
"fields" : {
"content" : {"index" : "no"},
"title" : {"store" : "yes"},
"date" : {"store" : "yes"},
"content" : {"index" : true},
"title" : {"store" : true},
"date" : {"store" : true},
"author" : {"analyzer" : "my_analyzer"},
"keywords" : {"store" : "yes"},
"content_type" : {"store" : "yes"},
"content_length" : {"store" : "yes"},
"language" : {"store" : "yes"}
"keywords" : {"store" : true},
"content_type" : {"store" : true},
"content_length" : {"store" : true},
"language" : {"store" : true}
}
}
}

View File

@ -127,7 +127,7 @@ experimental[The format of the additional detail information is experimental and
GET _analyze
{
"tokenizer" : "standard",
"token_filter" : ["snowball"],
"filter" : ["snowball"],
"text" : "detailed output",
"explain" : true,
"attributes" : ["keyword"] <1>

View File

@ -1,6 +1,9 @@
[[lat-lon]]
=== `lat_lon`
deprecated[5.0.0, ????????]
// https://github.com/elastic/elasticsearch/issues/19792
<<geo-queries,Geo-queries>> are usually performed by plugging the value of
each <<geo-point,`geo_point`>> field into a formula to determine whether it
falls into the required area or not. Unlike most queries, the inverted index
@ -10,7 +13,7 @@ Setting `lat_lon` to `true` causes the latitude and longitude values to be
indexed as numeric fields (called `.lat` and `.lon`). These fields can be used
by the <<query-dsl-geo-bounding-box-query,`geo_bounding_box`>> and
<<query-dsl-geo-distance-query,`geo_distance`>> queries instead of
performing in-memory calculations.
performing in-memory calculations. So this mapping:
[source,js]
--------------------------------------------------
@ -27,8 +30,15 @@ PUT my_index
}
}
}
--------------------------------------------------
// TEST[warning:geo_point lat_lon parameter is deprecated and will be removed in the next major release]
<1> Setting `lat_lon` to true indexes the geo-point in the `location.lat` and `location.lon` fields.
PUT my_index/my_type/1
Allows these actions:
[source,js]
--------------------------------------------------
PUT my_index/my_type/1?refresh
{
"location": {
"lat": 41.12,
@ -46,18 +56,17 @@ GET my_index/_search
"lon": -71
},
"distance": "50km",
"optimize_bbox": "indexed" <2>
"optimize_bbox": "indexed" <1>
}
}
}
--------------------------------------------------
// CONSOLE
<1> Setting `lat_lon` to true indexes the geo-point in the `location.lat` and `location.lon` fields.
<2> The `indexed` option tells the geo-distance query to use the inverted index instead of the in-memory calculation.
// TEST[continued]
<1> The `indexed` option tells the geo-distance query to use the inverted index instead of the in-memory calculation.
Whether the in-memory or indexed operation performs better depends both on
your dataset and on the types of queries that you are running.
NOTE: The `lat_lon` option only makes sense for single-value `geo_point`
fields. It will not work with arrays of geo-points.

View File

@ -18,7 +18,7 @@ GET /_search
{
"query": {
"function_score": {
"query": {},
"query": { "match_all": {} },
"boost": "5",
"random_score": {}, <1>
"boost_mode":"multiply"
@ -40,16 +40,16 @@ GET /_search
{
"query": {
"function_score": {
"query": {},
"query": { "match_all": {} },
"boost": "5", <1>
"functions": [
{
"filter": {},
"filter": { "match": { "test": "bar" } },
"random_score": {}, <2>
"weight": 23
},
{
"filter": {},
"filter": { "match": { "test": "cat" } },
"weight": 42
}
],

View File

@ -1,6 +1,8 @@
[[query-dsl-indices-query]]
=== Indices Query
deprecated[5.0.0, Search on the '_index' field instead]
The `indices` query is useful in cases where a search is executed across
multiple indices. It allows to specify a list of index names and an inner
query that is only executed for indices matching names on that list.
@ -21,6 +23,7 @@ GET /_search
}
--------------------------------------------------
// CONSOLE
// TEST[warning:indices query is deprecated. Instead search on the '_index' field]
You can use the `index` field to provide a single index.

View File

@ -6,7 +6,7 @@ set of documents. In order to do so, MLT selects a set of representative terms
of these input documents, forms a query using these terms, executes the query
and returns the results. The user controls the input documents, how the terms
should be selected and how the query is formed. `more_like_this` can be
shortened to `mlt` deprecated[5.0.0,use `more_like_this` instead).
shortened to `mlt` deprecated[5.0.0,use `more_like_this` instead].
The simplest use case consists of asking for documents that are similar to a
provided piece of text. Here, we are asking for all movies that have some text

View File

@ -57,7 +57,7 @@ GET /my_index/_search
{
"query": {
"has_parent": {
"type": "blog_post",
"parent_type": "blog_post",
"query": {
"term": {
"_id": "1"

View File

@ -19,7 +19,7 @@ PUT /my-index
"doctype": {
"properties": {
"message": {
"type": "string"
"type": "keyword"
}
}
},

View File

@ -28,7 +28,7 @@ GET /_search
--------------------------------------------------
// CONSOLE
Or :
Or with the `prefix` deprecated[5.0.0, Use `value`] syntax:
[source,js]
--------------------------------------------------
@ -39,6 +39,7 @@ GET /_search
}
--------------------------------------------------
// CONSOLE
// TEST[warning:Deprecated field [prefix] used, expected [value] instead]
This multi term query allows you to control how it gets rewritten using the
<<query-dsl-multi-term-rewrite,rewrite>>

View File

@ -1,6 +1,8 @@
[[query-dsl-template-query]]
=== Template Query
deprecated[5.0.0, Use the <<search-template>> API]
A query that accepts a query template and a map of key/value pairs to fill in
template parameters. Templating is based on Mustache. For simple token substitution all you provide
is a query containing some variable that you want to substitute and the actual
@ -21,6 +23,7 @@ GET /_search
}
------------------------------------------
// CONSOLE
// TEST[warning:[template] query is deprecated, use search template api instead]
The above request is translated into:
@ -54,6 +57,7 @@ GET /_search
}
------------------------------------------
// CONSOLE
// TEST[warning:[template] query is deprecated, use search template api instead]
<1> New line characters (`\n`) should be escaped as `\\n` or removed,
and quotes (`"`) should be escaped as `\\"`.
@ -80,6 +84,7 @@ GET /_search
}
------------------------------------------
// CONSOLE
// TEST[warning:[template] query is deprecated, use search template api instead]
<1> Name of the query template in `config/scripts/`, i.e., `my_template.mustache`.
@ -113,11 +118,10 @@ GET /_search
------------------------------------------
// CONSOLE
// TEST[continued]
// TEST[warning:[template] query is deprecated, use search template api instead]
<1> Name of the query template in `config/scripts/`, i.e., `my_template.mustache`.
There is also a dedicated `template` endpoint, allows you to template an entire search request.
Please see <<search-template>> for more details.

View File

@ -387,7 +387,7 @@ GET /_search
"match_phrase": {
"content": {
"query": "foo bar",
"phrase_slop": 1
"slop": 1
}
}
},
@ -413,7 +413,7 @@ GET /_search
"match_phrase": {
"content": {
"query": "foo bar",
"phrase_slop": 1,
"slop": 1,
"boost": 10.0
}
}

View File

@ -53,15 +53,16 @@ GET /_search
--------------------------------------------------
// CONSOLE
Finally, for complete control, you can specify both include and exclude patterns:
Finally, for complete control, you can specify both `includes` and `excludes`
patterns:
[source,js]
--------------------------------------------------
GET /_search
{
"_source": {
"include": [ "obj1.*", "obj2.*" ],
"exclude": [ "*.description" ]
"includes": [ "obj1.*", "obj2.*" ],
"excludes": [ "*.description" ]
},
"query" : {
"term" : { "user" : "kimchy" }

View File

@ -1,5 +1,7 @@
---
"Template query":
- skip:
features: warnings
- do:
index:
@ -23,54 +25,72 @@
- match: { acknowledged: true }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "inline": { "term": { "text": { "value": "{{template}}" } } }, "params": { "template": "value1" } } } }
- match: { hits.total: 1 }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "file": "file_query_template", "params": { "my_value": "value1" } } } }
- match: { hits.total: 1 }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "id": "1", "params": { "my_value": "value1" } } } }
- match: { hits.total: 1 }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "id": "/mustache/1", "params": { "my_value": "value1" } } } }
- match: { hits.total: 1 }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "inline": {"match_{{template}}": {}}, "params" : { "template" : "all" } } } }
- match: { hits.total: 2 }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "inline": "{ \"term\": { \"text\": { \"value\": \"{{template}}\" } } }", "params": { "template": "value1" } } } }
- match: { hits.total: 1 }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "inline": "{\"match_{{template}}\": {}}", "params" : { "template" : "all" } } } }
- match: { hits.total: 2 }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "inline": "{\"match_all\": {}}", "params" : {} } } }
- match: { hits.total: 2 }
- do:
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "inline": "{\"query_string\": { \"query\" : \"{{query}}\" }}", "params" : { "query" : "text:\"value2 value3\"" } } } }
- match: { hits.total: 1 }

View File

@ -173,6 +173,25 @@ The argument to `catch` can be any of:
If `catch` is specified, then the `response` var must be cleared, and the test
should fail if no error is thrown.
If the arguments to `do` include `warnings` then we are expecting a `Warning`
header to come back from the request. If the arguments *don't* include a
`warnings` argument then we *don't* expect the response to include a `Warning`
header. The warnings must match exactly. Using it looks like this:
....
- do:
warnings:
- '[index] is deprecated'
- quotes are not required because yaml
- but this argument is always a list, never a single string
- no matter how many warnings you expect
get:
index: test
type: test
id: 1
....
=== `set`
For some tests, it is necessary to extract a value from the previous `response`, in
@ -284,4 +303,3 @@ This depends on the datatype of the value being examined, eg:
- length: { _tokens: 3 } # the `_tokens` array has 3 elements
- length: { _source: 5 } # the `_source` hash has 5 keys
....

View File

@ -41,7 +41,7 @@
body:
query:
more_like_this:
docs:
like:
-
_index: test_1
_type: test
@ -51,8 +51,8 @@
_index: test_1
_type: test
_id: 2
ids:
- 3
-
_id: 3
include: true
min_doc_freq: 0
min_term_freq: 0

View File

@ -72,7 +72,7 @@ setup:
search:
body:
_source:
include: [ include.field1, include.field2 ]
includes: [ include.field1, include.field2 ]
query: { match_all: {} }
- match: { hits.hits.0._source.include.field1: v1 }
- match: { hits.hits.0._source.include.field2: v2 }

View File

@ -18,6 +18,7 @@
*/
package org.elasticsearch.test.rest.yaml;
import org.apache.http.Header;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.Response;
@ -25,6 +26,8 @@ import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* Response obtained from a REST call, eagerly reads the response body into a string for later optional parsing.
@ -70,6 +73,19 @@ public class ClientYamlTestResponse {
return response.getStatusLine().getReasonPhrase();
}
/**
* Get a list of all of the values of all warning headers returned in the response.
*/
public List<String> getWarningHeaders() {
List<String> warningHeaders = new ArrayList<>();
for (Header header : response.getHeaders()) {
if (header.getName().equals("Warning")) {
warningHeaders.add(header.getValue());
}
}
return warningHeaders;
}
/**
* Returns the body properly parsed depending on the content type.
* Might be a string or a json object parsed as a map.

View File

@ -41,6 +41,7 @@ public final class Features {
"groovy_scripting",
"headers",
"stash_in_path",
"warnings",
"yaml"));
private Features() {

View File

@ -18,6 +18,8 @@
*/
package org.elasticsearch.test.rest.yaml.parser;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.ParseFieldMatcherSupplier;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.xcontent.XContentLocation;
import org.elasticsearch.common.xcontent.XContentParser;
@ -36,7 +38,7 @@ import java.util.Map;
* Context shared across the whole tests parse phase.
* Provides shared parse methods and holds information needed to parse the test sections (e.g. es version)
*/
public class ClientYamlTestSuiteParseContext {
public class ClientYamlTestSuiteParseContext implements ParseFieldMatcherSupplier {
private static final SetupSectionParser SETUP_SECTION_PARSER = new SetupSectionParser();
private static final TeardownSectionParser TEARDOWN_SECTION_PARSER = new TeardownSectionParser();
@ -185,4 +187,9 @@ public class ClientYamlTestSuiteParseContext {
Map.Entry<String, Object> entry = map.entrySet().iterator().next();
return Tuple.tuple(entry.getKey(), entry.getValue());
}
@Override
public ParseFieldMatcher getParseFieldMatcher() {
return ParseFieldMatcher.STRICT;
}
}

View File

@ -18,6 +18,7 @@
*/
package org.elasticsearch.test.rest.yaml.parser;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
@ -25,9 +26,13 @@ import org.elasticsearch.test.rest.yaml.section.ApiCallSection;
import org.elasticsearch.test.rest.yaml.section.DoSection;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.Collections.unmodifiableList;
/**
* Parser for do sections
*/
@ -44,6 +49,7 @@ public class DoSectionParser implements ClientYamlTestFragmentParser<DoSection>
DoSection doSection = new DoSection(parseContext.parser().getTokenLocation());
ApiCallSection apiCallSection = null;
Map<String, String> headers = new HashMap<>();
List<String> expectedWarnings = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
@ -52,6 +58,17 @@ public class DoSectionParser implements ClientYamlTestFragmentParser<DoSection>
if ("catch".equals(currentFieldName)) {
doSection.setCatch(parser.text());
}
} else if (token == XContentParser.Token.START_ARRAY) {
if ("warnings".equals(currentFieldName)) {
while ((token = parser.nextToken()) == XContentParser.Token.VALUE_STRING) {
expectedWarnings.add(parser.text());
}
if (token != XContentParser.Token.END_ARRAY) {
throw new ParsingException(parser.getTokenLocation(), "[warnings] must be a string array but saw [" + token + "]");
}
} else {
throw new ParsingException(parser.getTokenLocation(), "unknown array [" + currentFieldName + "]");
}
} else if (token == XContentParser.Token.START_OBJECT) {
if ("headers".equals(currentFieldName)) {
String headerName = null;
@ -97,6 +114,7 @@ public class DoSectionParser implements ClientYamlTestFragmentParser<DoSection>
apiCallSection.addHeaders(headers);
}
doSection.setApiCallSection(apiCallSection);
doSection.setExpectedWarningHeaders(unmodifiableList(expectedWarnings));
} finally {
parser.nextToken();
}

View File

@ -29,8 +29,12 @@ import org.elasticsearch.test.rest.yaml.ClientYamlTestResponseException;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.util.Collections.emptyList;
import static org.elasticsearch.common.collect.Tuple.tuple;
import static org.elasticsearch.test.hamcrest.RegexMatcher.matches;
import static org.hamcrest.Matchers.allOf;
@ -49,6 +53,10 @@ import static org.junit.Assert.fail;
* headers:
* Authorization: Basic user:pass
* Content-Type: application/json
* warnings:
* - Stuff is deprecated, yo
* - Don't use deprecated stuff
* - Please, stop. It hurts.
* update:
* index: test_1
* type: test
@ -63,6 +71,7 @@ public class DoSection implements ExecutableSection {
private final XContentLocation location;
private String catchParam;
private ApiCallSection apiCallSection;
private List<String> expectedWarningHeaders = emptyList();
public DoSection(XContentLocation location) {
this.location = location;
@ -84,6 +93,22 @@ public class DoSection implements ExecutableSection {
this.apiCallSection = apiCallSection;
}
/**
* Warning headers that we expect from this response. If the headers don't match exactly this request is considered to have failed.
* Defaults to emptyList.
*/
public List<String> getExpectedWarningHeaders() {
return expectedWarningHeaders;
}
/**
* Set the warning headers that we expect from this response. If the headers don't match exactly this request is considered to have
* failed. Defaults to emptyList.
*/
public void setExpectedWarningHeaders(List<String> expectedWarningHeaders) {
this.expectedWarningHeaders = expectedWarningHeaders;
}
@Override
public XContentLocation getLocation() {
return location;
@ -100,7 +125,7 @@ public class DoSection implements ExecutableSection {
}
try {
ClientYamlTestResponse restTestResponse = executionContext.callApi(apiCallSection.getApi(), apiCallSection.getParams(),
ClientYamlTestResponse response = executionContext.callApi(apiCallSection.getApi(), apiCallSection.getParams(),
apiCallSection.getBodies(), apiCallSection.getHeaders());
if (Strings.hasLength(catchParam)) {
String catchStatusCode;
@ -111,8 +136,9 @@ public class DoSection implements ExecutableSection {
} else {
throw new UnsupportedOperationException("catch value [" + catchParam + "] not supported");
}
fail(formatStatusCodeMessage(restTestResponse, catchStatusCode));
fail(formatStatusCodeMessage(response, catchStatusCode));
}
checkWarningHeaders(response.getWarningHeaders());
} catch(ClientYamlTestResponseException e) {
ClientYamlTestResponse restTestResponse = e.getRestTestResponse();
if (!Strings.hasLength(catchParam)) {
@ -135,6 +161,39 @@ public class DoSection implements ExecutableSection {
}
}
/**
* Check that the response contains only the warning headers that we expect.
*/
void checkWarningHeaders(List<String> warningHeaders) {
StringBuilder failureMessage = null;
// LinkedHashSet so that missing expected warnings come back in a predictable order which is nice for testing
Set<String> expected = new LinkedHashSet<>(expectedWarningHeaders);
for (String header : warningHeaders) {
if (expected.remove(header)) {
// Was expected, all good.
continue;
}
if (failureMessage == null) {
failureMessage = new StringBuilder("got unexpected warning headers [");
}
failureMessage.append('\n').append(header);
}
if (false == expected.isEmpty()) {
if (failureMessage == null) {
failureMessage = new StringBuilder();
} else {
failureMessage.append("\n] ");
}
failureMessage.append("didn't get expected warning headers [");
for (String header : expected) {
failureMessage.append('\n').append(header);
}
}
if (failureMessage != null) {
fail(failureMessage + "\n]");
}
}
private void assertStatusCode(ClientYamlTestResponse restTestResponse) {
Tuple<String, org.hamcrest.Matcher<Integer>> stringMatcherTuple = catches.get(catchParam);
assertThat(formatStatusCodeMessage(restTestResponse, stringMatcherTuple.v1()),

View File

@ -29,8 +29,10 @@ import org.elasticsearch.test.rest.yaml.section.DoSection;
import org.hamcrest.MatcherAssert;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import static java.util.Collections.singletonList;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
@ -400,6 +402,46 @@ public class DoSectionParserTests extends AbstractParserTestCase {
assertThat(doSection.getApiCallSection().getBodies().size(), equalTo(0));
}
public void testParseDoSectionExpectedWarnings() throws Exception {
parser = YamlXContent.yamlXContent.createParser(
"indices.get_field_mapping:\n" +
" index: test_index\n" +
" type: test_type\n" +
"warnings:\n" +
" - some test warning they are typically pretty long\n" +
" - some other test warning somtimes they have [in] them"
);
DoSectionParser doSectionParser = new DoSectionParser();
DoSection doSection = doSectionParser.parse(new ClientYamlTestSuiteParseContext("api", "suite", parser));
assertThat(doSection.getCatch(), nullValue());
assertThat(doSection.getApiCallSection(), notNullValue());
assertThat(doSection.getApiCallSection().getApi(), equalTo("indices.get_field_mapping"));
assertThat(doSection.getApiCallSection().getParams().size(), equalTo(2));
assertThat(doSection.getApiCallSection().getParams().get("index"), equalTo("test_index"));
assertThat(doSection.getApiCallSection().getParams().get("type"), equalTo("test_type"));
assertThat(doSection.getApiCallSection().hasBody(), equalTo(false));
assertThat(doSection.getApiCallSection().getBodies().size(), equalTo(0));
assertThat(doSection.getExpectedWarningHeaders(), equalTo(Arrays.asList(
"some test warning they are typically pretty long",
"some other test warning somtimes they have [in] them")));
parser = YamlXContent.yamlXContent.createParser(
"indices.get_field_mapping:\n" +
" index: test_index\n" +
"warnings:\n" +
" - just one entry this time"
);
doSection = doSectionParser.parse(new ClientYamlTestSuiteParseContext("api", "suite", parser));
assertThat(doSection.getCatch(), nullValue());
assertThat(doSection.getApiCallSection(), notNullValue());
assertThat(doSection.getExpectedWarningHeaders(), equalTo(singletonList(
"just one entry this time")));
}
private static void assertJsonEquals(Map<String, Object> actual, String expected) throws IOException {
Map<String,Object> expectedMap;
try (XContentParser parser = JsonXContent.jsonXContent.createParser(expected)) {

View File

@ -0,0 +1,66 @@
/*
* 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.yaml.section;
import org.elasticsearch.common.xcontent.XContentLocation;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.Arrays;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
public class DoSectionTests extends ESTestCase {
public void testWarningHeaders() throws IOException {
DoSection section = new DoSection(new XContentLocation(1, 1));
// No warning headers doesn't throw an exception
section.checkWarningHeaders(emptyList());
// Any warning headers fail
AssertionError e = expectThrows(AssertionError.class, () -> section.checkWarningHeaders(singletonList("test")));
assertEquals("got unexpected warning headers [\ntest\n]", e.getMessage());
e = expectThrows(AssertionError.class, () -> section.checkWarningHeaders(Arrays.asList("test", "another", "some more")));
assertEquals("got unexpected warning headers [\ntest\nanother\nsome more\n]", e.getMessage());
// But not when we expect them
section.setExpectedWarningHeaders(singletonList("test"));
section.checkWarningHeaders(singletonList("test"));
section.setExpectedWarningHeaders(Arrays.asList("test", "another", "some more"));
section.checkWarningHeaders(Arrays.asList("test", "another", "some more"));
// But if you don't get some that you did expect, that is an error
section.setExpectedWarningHeaders(singletonList("test"));
e = expectThrows(AssertionError.class, () -> section.checkWarningHeaders(emptyList()));
assertEquals("didn't get expected warning headers [\ntest\n]", e.getMessage());
section.setExpectedWarningHeaders(Arrays.asList("test", "another", "some more"));
e = expectThrows(AssertionError.class, () -> section.checkWarningHeaders(emptyList()));
assertEquals("didn't get expected warning headers [\ntest\nanother\nsome more\n]", e.getMessage());
e = expectThrows(AssertionError.class, () -> section.checkWarningHeaders(Arrays.asList("test", "some more")));
assertEquals("didn't get expected warning headers [\nanother\n]", e.getMessage());
// It is also an error if you get some warning you want and some you don't want
section.setExpectedWarningHeaders(Arrays.asList("test", "another", "some more"));
e = expectThrows(AssertionError.class, () -> section.checkWarningHeaders(Arrays.asList("test", "cat")));
assertEquals("got unexpected warning headers [\ncat\n] didn't get expected warning headers [\nanother\nsome more\n]",
e.getMessage());
}
}