[Test] Extend parsing checks for DocWriteResponses (#25257)
This commit changes the parsing logic of DocWriteResponse, ReplicationResponse and GetResult so that it skips any unknown additional fields (for forward compatibility reasons). This affects the IndexResponse, UpdateResponse,DeleteResponse and GetResponse objects.
This commit is contained in:
parent
bcaa413b0b
commit
e4f4886d40
|
@ -395,6 +395,10 @@ public class RestHighLevelClient {
|
||||||
try {
|
try {
|
||||||
return responseConverter.apply(e.getResponse());
|
return responseConverter.apply(e.getResponse());
|
||||||
} catch (Exception innerException) {
|
} catch (Exception innerException) {
|
||||||
|
//the exception is ignored as we now try to parse the response as an error.
|
||||||
|
//this covers cases like get where 404 can either be a valid document not found response,
|
||||||
|
//or an error for which parsing is completely different. We try to consider the 404 response as a valid one
|
||||||
|
//first. If parsing of the response breaks, we fall back to parsing it as an error.
|
||||||
throw parseResponseException(e);
|
throw parseResponseException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,8 +43,6 @@ import java.net.URLEncoder;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
||||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownField;
|
|
||||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownToken;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A base class for the response of a write operation that involves a single doc
|
* A base class for the response of a write operation that involves a single doc
|
||||||
|
@ -351,17 +349,15 @@ public abstract class DocWriteResponse extends ReplicationResponse implements Wr
|
||||||
context.setSeqNo(parser.longValue());
|
context.setSeqNo(parser.longValue());
|
||||||
} else if (_PRIMARY_TERM.equals(currentFieldName)) {
|
} else if (_PRIMARY_TERM.equals(currentFieldName)) {
|
||||||
context.setPrimaryTerm(parser.longValue());
|
context.setPrimaryTerm(parser.longValue());
|
||||||
} else {
|
|
||||||
throwUnknownField(currentFieldName, parser.getTokenLocation());
|
|
||||||
}
|
}
|
||||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||||
if (_SHARDS.equals(currentFieldName)) {
|
if (_SHARDS.equals(currentFieldName)) {
|
||||||
context.setShardInfo(ShardInfo.fromXContent(parser));
|
context.setShardInfo(ShardInfo.fromXContent(parser));
|
||||||
} else {
|
} else {
|
||||||
throwUnknownField(currentFieldName, parser.getTokenLocation());
|
parser.skipChildren(); // skip potential inner objects for forward compatibility
|
||||||
}
|
}
|
||||||
} else {
|
} else if (token == XContentParser.Token.START_ARRAY) {
|
||||||
throwUnknownToken(token, parser.getTokenLocation());
|
parser.skipChildren(); // skip potential inner arrays for forward compatibility
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.elasticsearch.action.get;
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchParseException;
|
import org.elasticsearch.ElasticsearchParseException;
|
||||||
import org.elasticsearch.action.ActionResponse;
|
import org.elasticsearch.action.ActionResponse;
|
||||||
|
import org.elasticsearch.common.ParsingException;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
@ -33,6 +34,7 @@ import org.elasticsearch.index.get.GetResult;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@ -158,8 +160,32 @@ public class GetResponse extends ActionResponse implements Iterable<GetField>, T
|
||||||
return getResult.toXContent(builder, params);
|
return getResult.toXContent(builder, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method can be used to parse a {@link GetResponse} object when it has been printed out
|
||||||
|
* as a xcontent using the {@link #toXContent(XContentBuilder, Params)} method.
|
||||||
|
* <p>
|
||||||
|
* For forward compatibility reason this method might not fail if it tries to parse a field it
|
||||||
|
* doesn't know. But before returning the result it will check that enough information were
|
||||||
|
* parsed to return a valid {@link GetResponse} instance and throws a {@link ParsingException}
|
||||||
|
* otherwise. This is the case when we get a 404 back, which can be parsed as a normal
|
||||||
|
* {@link GetResponse} with found set to false, or as an elasticsearch exception. The caller
|
||||||
|
* of this method needs a way to figure out whether we got back a valid get response, which
|
||||||
|
* can be done by catching ParsingException.
|
||||||
|
*
|
||||||
|
* @param parser {@link XContentParser} to parse the response from
|
||||||
|
* @return a {@link GetResponse}
|
||||||
|
* @throws IOException is an I/O exception occurs during the parsing
|
||||||
|
*/
|
||||||
public static GetResponse fromXContent(XContentParser parser) throws IOException {
|
public static GetResponse fromXContent(XContentParser parser) throws IOException {
|
||||||
GetResult getResult = GetResult.fromXContent(parser);
|
GetResult getResult = GetResult.fromXContent(parser);
|
||||||
|
|
||||||
|
// At this stage we ensure that we parsed enough information to return
|
||||||
|
// a valid GetResponse instance. If it's not the case, we throw an
|
||||||
|
// exception so that callers know it and can handle it correctly.
|
||||||
|
if (getResult.getIndex() == null && getResult.getType() == null && getResult.getId() == null) {
|
||||||
|
throw new ParsingException(parser.getTokenLocation(),
|
||||||
|
String.format(Locale.ROOT, "Missing required fields [%s,%s,%s]", GetResult._INDEX, GetResult._TYPE, GetResult._ID));
|
||||||
|
}
|
||||||
return new GetResponse(getResult);
|
return new GetResponse(getResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,6 @@ import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
||||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownField;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for write action responses.
|
* Base class for write action responses.
|
||||||
|
@ -187,8 +186,8 @@ public class ReplicationResponse extends ActionResponse {
|
||||||
total = parser.intValue();
|
total = parser.intValue();
|
||||||
} else if (SUCCESSFUL.equals(currentFieldName)) {
|
} else if (SUCCESSFUL.equals(currentFieldName)) {
|
||||||
successful = parser.intValue();
|
successful = parser.intValue();
|
||||||
} else if (FAILED.equals(currentFieldName) == false) {
|
} else {
|
||||||
throwUnknownField(currentFieldName, parser.getTokenLocation());
|
parser.skipChildren();
|
||||||
}
|
}
|
||||||
} else if (token == XContentParser.Token.START_ARRAY) {
|
} else if (token == XContentParser.Token.START_ARRAY) {
|
||||||
if (FAILURES.equals(currentFieldName)) {
|
if (FAILURES.equals(currentFieldName)) {
|
||||||
|
@ -197,8 +196,10 @@ public class ReplicationResponse extends ActionResponse {
|
||||||
failuresList.add(Failure.fromXContent(parser));
|
failuresList.add(Failure.fromXContent(parser));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throwUnknownField(currentFieldName, parser.getTokenLocation());
|
parser.skipChildren(); // skip potential inner arrays for forward compatibility
|
||||||
}
|
}
|
||||||
|
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||||
|
parser.skipChildren(); // skip potential inner arrays for forward compatibility
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Failure[] failures = EMPTY;
|
Failure[] failures = EMPTY;
|
||||||
|
@ -365,15 +366,15 @@ public class ReplicationResponse extends ActionResponse {
|
||||||
status = RestStatus.valueOf(parser.text());
|
status = RestStatus.valueOf(parser.text());
|
||||||
} else if (PRIMARY.equals(currentFieldName)) {
|
} else if (PRIMARY.equals(currentFieldName)) {
|
||||||
primary = parser.booleanValue();
|
primary = parser.booleanValue();
|
||||||
} else {
|
|
||||||
throwUnknownField(currentFieldName, parser.getTokenLocation());
|
|
||||||
}
|
}
|
||||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||||
if (REASON.equals(currentFieldName)) {
|
if (REASON.equals(currentFieldName)) {
|
||||||
reason = ElasticsearchException.fromXContent(parser);
|
reason = ElasticsearchException.fromXContent(parser);
|
||||||
} else {
|
} else {
|
||||||
throwUnknownField(currentFieldName, parser.getTokenLocation());
|
parser.skipChildren(); // skip potential inner objects for forward compatibility
|
||||||
}
|
}
|
||||||
|
} else if (token == XContentParser.Token.START_ARRAY) {
|
||||||
|
parser.skipChildren(); // skip potential inner arrays for forward compatibility
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Failure(new ShardId(shardIndex, IndexMetaData.INDEX_UUID_NA_VALUE, shardId), nodeId, reason, status, primary);
|
return new Failure(new ShardId(shardIndex, IndexMetaData.INDEX_UUID_NA_VALUE, shardId), nodeId, reason, status, primary);
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
package org.elasticsearch.index.get;
|
package org.elasticsearch.index.get;
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchParseException;
|
import org.elasticsearch.ElasticsearchParseException;
|
||||||
|
import org.elasticsearch.common.ParsingException;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.compress.CompressorFactory;
|
import org.elasticsearch.common.compress.CompressorFactory;
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
@ -43,14 +44,13 @@ import java.util.Objects;
|
||||||
|
|
||||||
import static java.util.Collections.emptyMap;
|
import static java.util.Collections.emptyMap;
|
||||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
||||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownField;
|
|
||||||
import static org.elasticsearch.index.get.GetField.readGetField;
|
import static org.elasticsearch.index.get.GetField.readGetField;
|
||||||
|
|
||||||
public class GetResult implements Streamable, Iterable<GetField>, ToXContentObject {
|
public class GetResult implements Streamable, Iterable<GetField>, ToXContentObject {
|
||||||
|
|
||||||
private static final String _INDEX = "_index";
|
public static final String _INDEX = "_index";
|
||||||
private static final String _TYPE = "_type";
|
public static final String _TYPE = "_type";
|
||||||
private static final String _ID = "_id";
|
public static final String _ID = "_id";
|
||||||
private static final String _VERSION = "_version";
|
private static final String _VERSION = "_version";
|
||||||
private static final String FOUND = "found";
|
private static final String FOUND = "found";
|
||||||
private static final String FIELDS = "fields";
|
private static final String FIELDS = "fields";
|
||||||
|
@ -273,7 +273,7 @@ public class GetResult implements Streamable, Iterable<GetField>, ToXContentObje
|
||||||
String currentFieldName = parser.currentName();
|
String currentFieldName = parser.currentName();
|
||||||
String index = null, type = null, id = null;
|
String index = null, type = null, id = null;
|
||||||
long version = -1;
|
long version = -1;
|
||||||
boolean found = false;
|
Boolean found = null;
|
||||||
BytesReference source = null;
|
BytesReference source = null;
|
||||||
Map<String, GetField> fields = new HashMap<>();
|
Map<String, GetField> fields = new HashMap<>();
|
||||||
while((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
while((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||||
|
@ -307,8 +307,10 @@ public class GetResult implements Streamable, Iterable<GetField>, ToXContentObje
|
||||||
fields.put(getField.getName(), getField);
|
fields.put(getField.getName(), getField);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throwUnknownField(currentFieldName, parser.getTokenLocation());
|
parser.skipChildren(); // skip potential inner objects for forward compatibility
|
||||||
}
|
}
|
||||||
|
} else if (token == XContentParser.Token.START_ARRAY) {
|
||||||
|
parser.skipChildren(); // skip potential inner arrays for forward compatibility
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new GetResult(index, type, id, version, found, source, fields);
|
return new GetResult(index, type, id, version, found, source, fields);
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.elasticsearch.action.DocWriteResponse;
|
||||||
import org.elasticsearch.action.bulk.BulkItemResponse.Failure;
|
import org.elasticsearch.action.bulk.BulkItemResponse.Failure;
|
||||||
import org.elasticsearch.action.delete.DeleteResponseTests;
|
import org.elasticsearch.action.delete.DeleteResponseTests;
|
||||||
import org.elasticsearch.action.index.IndexResponseTests;
|
import org.elasticsearch.action.index.IndexResponseTests;
|
||||||
|
import org.elasticsearch.action.update.UpdateResponse;
|
||||||
import org.elasticsearch.action.update.UpdateResponseTests;
|
import org.elasticsearch.action.update.UpdateResponseTests;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.collect.Tuple;
|
import org.elasticsearch.common.collect.Tuple;
|
||||||
|
@ -137,11 +138,12 @@ public class BulkItemResponseTests extends ESTestCase {
|
||||||
|
|
||||||
assertDeepEquals((ElasticsearchException) expectedFailure.getCause(), (ElasticsearchException) actualFailure.getCause());
|
assertDeepEquals((ElasticsearchException) expectedFailure.getCause(), (ElasticsearchException) actualFailure.getCause());
|
||||||
} else {
|
} else {
|
||||||
|
DocWriteResponse expectedDocResponse = expected.getResponse();
|
||||||
|
DocWriteResponse actualDocResponse = expected.getResponse();
|
||||||
|
|
||||||
|
IndexResponseTests.assertDocWriteResponse(expectedDocResponse, actualDocResponse);
|
||||||
if (expected.getOpType() == DocWriteRequest.OpType.UPDATE) {
|
if (expected.getOpType() == DocWriteRequest.OpType.UPDATE) {
|
||||||
UpdateResponseTests.assertUpdateResponse(expected.getResponse(), actual.getResponse());
|
assertEquals(((UpdateResponse) expectedDocResponse).getGetResult(), ((UpdateResponse)actualDocResponse).getGetResult());
|
||||||
} else {
|
|
||||||
// assertDocWriteResponse check the result for INDEX/CREATE and DELETE operations
|
|
||||||
IndexResponseTests.assertDocWriteResponse(expected.getResponse(), actual.getResponse());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,9 +32,11 @@ import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.test.RandomObjects;
|
import org.elasticsearch.test.RandomObjects;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import static org.elasticsearch.action.index.IndexResponseTests.assertDocWriteResponse;
|
import static org.elasticsearch.action.index.IndexResponseTests.assertDocWriteResponse;
|
||||||
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_UUID_NA_VALUE;
|
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_UUID_NA_VALUE;
|
||||||
|
import static org.elasticsearch.test.XContentTestUtils.insertRandomFields;
|
||||||
|
|
||||||
public class DeleteResponseTests extends ESTestCase {
|
public class DeleteResponseTests extends ESTestCase {
|
||||||
|
|
||||||
|
@ -56,16 +58,40 @@ public class DeleteResponseTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testToAndFromXContent() throws IOException {
|
public void testToAndFromXContent() throws IOException {
|
||||||
|
doFromXContentTestWithRandomFields(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test adds random fields and objects to the xContent rendered out to
|
||||||
|
* ensure we can parse it back to be forward compatible with additions to
|
||||||
|
* the xContent
|
||||||
|
*/
|
||||||
|
public void testFromXContentWithRandomFields() throws IOException {
|
||||||
|
doFromXContentTestWithRandomFields(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doFromXContentTestWithRandomFields(boolean addRandomFields) throws IOException {
|
||||||
final Tuple<DeleteResponse, DeleteResponse> tuple = randomDeleteResponse();
|
final Tuple<DeleteResponse, DeleteResponse> tuple = randomDeleteResponse();
|
||||||
DeleteResponse deleteResponse = tuple.v1();
|
DeleteResponse deleteResponse = tuple.v1();
|
||||||
DeleteResponse expectedDeleteResponse = tuple.v2();
|
DeleteResponse expectedDeleteResponse = tuple.v2();
|
||||||
|
|
||||||
boolean humanReadable = randomBoolean();
|
boolean humanReadable = randomBoolean();
|
||||||
final XContentType xContentType = randomFrom(XContentType.values());
|
final XContentType xContentType = randomFrom(XContentType.values());
|
||||||
BytesReference deleteResponseBytes = toShuffledXContent(deleteResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable);
|
BytesReference originalBytes = toShuffledXContent(deleteResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable);
|
||||||
|
|
||||||
|
BytesReference mutated;
|
||||||
|
if (addRandomFields) {
|
||||||
|
// The ShardInfo.Failure's exception is rendered out in a "reason" object. We shouldn't add anything random there
|
||||||
|
// because exception rendering and parsing are very permissive: any extra object or field would be rendered as
|
||||||
|
// a exception custom metadata and be parsed back as a custom header, making it impossible to compare the results
|
||||||
|
// in this test.
|
||||||
|
Predicate<String> excludeFilter = path -> path.contains("reason");
|
||||||
|
mutated = insertRandomFields(xContentType, originalBytes, excludeFilter, random());
|
||||||
|
} else {
|
||||||
|
mutated = originalBytes;
|
||||||
|
}
|
||||||
DeleteResponse parsedDeleteResponse;
|
DeleteResponse parsedDeleteResponse;
|
||||||
try (XContentParser parser = createParser(xContentType.xContent(), deleteResponseBytes)) {
|
try (XContentParser parser = createParser(xContentType.xContent(), mutated)) {
|
||||||
parsedDeleteResponse = DeleteResponse.fromXContent(parser);
|
parsedDeleteResponse = DeleteResponse.fromXContent(parser);
|
||||||
assertNull(parser.nextToken());
|
assertNull(parser.nextToken());
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.elasticsearch.action.get;
|
package org.elasticsearch.action.get;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.ParsingException;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.bytes.BytesArray;
|
import org.elasticsearch.common.bytes.BytesArray;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
|
@ -30,27 +31,53 @@ import org.elasticsearch.index.get.GetField;
|
||||||
import org.elasticsearch.index.get.GetResult;
|
import org.elasticsearch.index.get.GetResult;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
|
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
|
||||||
import static org.elasticsearch.index.get.GetResultTests.copyGetResult;
|
import static org.elasticsearch.index.get.GetResultTests.copyGetResult;
|
||||||
import static org.elasticsearch.index.get.GetResultTests.mutateGetResult;
|
import static org.elasticsearch.index.get.GetResultTests.mutateGetResult;
|
||||||
import static org.elasticsearch.index.get.GetResultTests.randomGetResult;
|
import static org.elasticsearch.index.get.GetResultTests.randomGetResult;
|
||||||
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
||||||
|
import static org.elasticsearch.test.XContentTestUtils.insertRandomFields;
|
||||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
|
||||||
|
|
||||||
public class GetResponseTests extends ESTestCase {
|
public class GetResponseTests extends ESTestCase {
|
||||||
|
|
||||||
public void testToAndFromXContent() throws Exception {
|
public void testToAndFromXContent() throws Exception {
|
||||||
|
doFromXContentTestWithRandomFields(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test adds random fields and objects to the xContent rendered out to
|
||||||
|
* ensure we can parse it back to be forward compatible with additions to
|
||||||
|
* the xContent
|
||||||
|
*/
|
||||||
|
public void testFromXContentWithRandomFields() throws IOException {
|
||||||
|
doFromXContentTestWithRandomFields(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doFromXContentTestWithRandomFields(boolean addRandomFields) throws IOException {
|
||||||
XContentType xContentType = randomFrom(XContentType.values());
|
XContentType xContentType = randomFrom(XContentType.values());
|
||||||
Tuple<GetResult, GetResult> tuple = randomGetResult(xContentType);
|
Tuple<GetResult, GetResult> tuple = randomGetResult(xContentType);
|
||||||
GetResponse getResponse = new GetResponse(tuple.v1());
|
GetResponse getResponse = new GetResponse(tuple.v1());
|
||||||
GetResponse expectedGetResponse = new GetResponse(tuple.v2());
|
GetResponse expectedGetResponse = new GetResponse(tuple.v2());
|
||||||
boolean humanReadable = randomBoolean();
|
boolean humanReadable = randomBoolean();
|
||||||
BytesReference originalBytes = toShuffledXContent(getResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable, "_source");
|
BytesReference originalBytes = toShuffledXContent(getResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable, "_source");
|
||||||
//test that we can parse what we print out
|
|
||||||
|
BytesReference mutated;
|
||||||
|
if (addRandomFields) {
|
||||||
|
// "_source" and "fields" just consists of key/value pairs, we shouldn't add anything random there. It is already
|
||||||
|
// randomized in the randomGetResult() method anyway. Also, we cannot add anything in the root object since this is
|
||||||
|
// where GetResult's metadata fields are rendered out while // other fields are rendered out in a "fields" object.
|
||||||
|
Predicate<String> excludeFilter = (s) -> s.isEmpty() || s.contains("fields") || s.contains("_source");
|
||||||
|
mutated = insertRandomFields(xContentType, originalBytes, excludeFilter, random());
|
||||||
|
} else {
|
||||||
|
mutated = originalBytes;
|
||||||
|
}
|
||||||
GetResponse parsedGetResponse;
|
GetResponse parsedGetResponse;
|
||||||
try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) {
|
try (XContentParser parser = createParser(xContentType.xContent(), mutated)) {
|
||||||
parsedGetResponse = GetResponse.fromXContent(parser);
|
parsedGetResponse = GetResponse.fromXContent(parser);
|
||||||
assertNull(parser.nextToken());
|
assertNull(parser.nextToken());
|
||||||
}
|
}
|
||||||
|
@ -90,6 +117,19 @@ public class GetResponseTests extends ESTestCase {
|
||||||
checkEqualsAndHashCode(new GetResponse(randomGetResult(XContentType.JSON).v1()), GetResponseTests::copyGetResponse,
|
checkEqualsAndHashCode(new GetResponse(randomGetResult(XContentType.JSON).v1()), GetResponseTests::copyGetResponse,
|
||||||
GetResponseTests::mutateGetResponse);
|
GetResponseTests::mutateGetResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testFromXContentThrowsParsingException() throws IOException {
|
||||||
|
GetResponse getResponse = new GetResponse(new GetResult(null, null, null, randomIntBetween(1, 5), randomBoolean(), null, null));
|
||||||
|
|
||||||
|
XContentType xContentType = randomFrom(XContentType.values());
|
||||||
|
BytesReference originalBytes = toShuffledXContent(getResponse, xContentType, ToXContent.EMPTY_PARAMS, randomBoolean());
|
||||||
|
|
||||||
|
try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) {
|
||||||
|
ParsingException exception = expectThrows(ParsingException.class, () -> GetResponse.fromXContent(parser));
|
||||||
|
assertEquals("Missing required fields [_index,_type,_id]", exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static GetResponse copyGetResponse(GetResponse getResponse) {
|
private static GetResponse copyGetResponse(GetResponse getResponse) {
|
||||||
return new GetResponse(copyGetResult(getResponse.getResult));
|
return new GetResponse(copyGetResult(getResponse.getResult));
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,11 @@ import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.test.RandomObjects;
|
import org.elasticsearch.test.RandomObjects;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import static org.elasticsearch.action.support.replication.ReplicationResponseTests.assertShardInfo;
|
import static org.elasticsearch.action.support.replication.ReplicationResponseTests.assertShardInfo;
|
||||||
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_UUID_NA_VALUE;
|
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_UUID_NA_VALUE;
|
||||||
|
import static org.elasticsearch.test.XContentTestUtils.insertRandomFields;
|
||||||
|
|
||||||
public class IndexResponseTests extends ESTestCase {
|
public class IndexResponseTests extends ESTestCase {
|
||||||
|
|
||||||
|
@ -57,16 +59,40 @@ public class IndexResponseTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testToAndFromXContent() throws IOException {
|
public void testToAndFromXContent() throws IOException {
|
||||||
|
doFromXContentTestWithRandomFields(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test adds random fields and objects to the xContent rendered out to
|
||||||
|
* ensure we can parse it back to be forward compatible with additions to
|
||||||
|
* the xContent
|
||||||
|
*/
|
||||||
|
public void testFromXContentWithRandomFields() throws IOException {
|
||||||
|
doFromXContentTestWithRandomFields(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doFromXContentTestWithRandomFields(boolean addRandomFields) throws IOException {
|
||||||
final Tuple<IndexResponse, IndexResponse> tuple = randomIndexResponse();
|
final Tuple<IndexResponse, IndexResponse> tuple = randomIndexResponse();
|
||||||
IndexResponse indexResponse = tuple.v1();
|
IndexResponse indexResponse = tuple.v1();
|
||||||
IndexResponse expectedIndexResponse = tuple.v2();
|
IndexResponse expectedIndexResponse = tuple.v2();
|
||||||
|
|
||||||
boolean humanReadable = randomBoolean();
|
boolean humanReadable = randomBoolean();
|
||||||
XContentType xContentType = randomFrom(XContentType.values());
|
XContentType xContentType = randomFrom(XContentType.values());
|
||||||
BytesReference indexResponseBytes = toShuffledXContent(indexResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable);
|
BytesReference originalBytes = toShuffledXContent(indexResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable);
|
||||||
|
|
||||||
|
BytesReference mutated;
|
||||||
|
if (addRandomFields) {
|
||||||
|
// The ShardInfo.Failure's exception is rendered out in a "reason" object. We shouldn't add anything random there
|
||||||
|
// because exception rendering and parsing are very permissive: any extra object or field would be rendered as
|
||||||
|
// a exception custom metadata and be parsed back as a custom header, making it impossible to compare the results
|
||||||
|
// in this test.
|
||||||
|
Predicate<String> excludeFilter = path -> path.contains("reason");
|
||||||
|
mutated = insertRandomFields(xContentType, originalBytes, excludeFilter, random());
|
||||||
|
} else {
|
||||||
|
mutated = originalBytes;
|
||||||
|
}
|
||||||
IndexResponse parsedIndexResponse;
|
IndexResponse parsedIndexResponse;
|
||||||
try (XContentParser parser = createParser(xContentType.xContent(), indexResponseBytes)) {
|
try (XContentParser parser = createParser(xContentType.xContent(), mutated)) {
|
||||||
parsedIndexResponse = IndexResponse.fromXContent(parser);
|
parsedIndexResponse = IndexResponse.fromXContent(parser);
|
||||||
assertNull(parser.nextToken());
|
assertNull(parser.nextToken());
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,11 +40,15 @@ import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import static org.elasticsearch.action.DocWriteResponse.Result.DELETED;
|
import static org.elasticsearch.action.DocWriteResponse.Result.DELETED;
|
||||||
import static org.elasticsearch.action.DocWriteResponse.Result.NOT_FOUND;
|
import static org.elasticsearch.action.DocWriteResponse.Result.NOT_FOUND;
|
||||||
import static org.elasticsearch.action.DocWriteResponse.Result.UPDATED;
|
import static org.elasticsearch.action.DocWriteResponse.Result.UPDATED;
|
||||||
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_UUID_NA_VALUE;
|
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_UUID_NA_VALUE;
|
||||||
|
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
|
||||||
|
import static org.elasticsearch.test.XContentTestUtils.insertRandomFields;
|
||||||
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
|
||||||
|
|
||||||
public class UpdateResponseTests extends ESTestCase {
|
public class UpdateResponseTests extends ESTestCase {
|
||||||
|
|
||||||
|
@ -81,29 +85,59 @@ public class UpdateResponseTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testToAndFromXContent() throws IOException {
|
public void testToAndFromXContent() throws IOException {
|
||||||
final XContentType xContentType = randomFrom(XContentType.values());
|
doFromXContentTestWithRandomFields(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test adds random fields and objects to the xContent rendered out to
|
||||||
|
* ensure we can parse it back to be forward compatible with additions to
|
||||||
|
* the xContent
|
||||||
|
*/
|
||||||
|
public void testFromXContentWithRandomFields() throws IOException {
|
||||||
|
doFromXContentTestWithRandomFields(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doFromXContentTestWithRandomFields(boolean addRandomFields) throws IOException {
|
||||||
|
final XContentType xContentType = randomFrom(XContentType.JSON);
|
||||||
final Tuple<UpdateResponse, UpdateResponse> tuple = randomUpdateResponse(xContentType);
|
final Tuple<UpdateResponse, UpdateResponse> tuple = randomUpdateResponse(xContentType);
|
||||||
UpdateResponse updateResponse = tuple.v1();
|
UpdateResponse updateResponse = tuple.v1();
|
||||||
UpdateResponse expectedUpdateResponse = tuple.v2();
|
UpdateResponse expectedUpdateResponse = tuple.v2();
|
||||||
|
|
||||||
boolean humanReadable = randomBoolean();
|
boolean humanReadable = randomBoolean();
|
||||||
BytesReference updateResponseBytes = toShuffledXContent(updateResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable);
|
BytesReference originalBytes = toShuffledXContent(updateResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable);
|
||||||
|
|
||||||
|
BytesReference mutated;
|
||||||
|
if (addRandomFields) {
|
||||||
|
// - The ShardInfo.Failure's exception is rendered out in a "reason" object. We shouldn't add anything random there
|
||||||
|
// because exception rendering and parsing are very permissive: any extra object or field would be rendered as
|
||||||
|
// a exception custom metadata and be parsed back as a custom header, making it impossible to compare the results
|
||||||
|
// in this test.
|
||||||
|
// - The GetResult's "_source" and "fields" just consists of key/value pairs, we shouldn't add anything random there.
|
||||||
|
// It is already randomized in the randomGetResult() method anyway. Also, we cannot add anything within the "get"
|
||||||
|
// object since this is where GetResult's metadata fields are rendered out and they would be parsed back as
|
||||||
|
// extra metadata fields.
|
||||||
|
Predicate<String> excludeFilter = path -> path.contains("reason") || path.contains("get");
|
||||||
|
mutated = insertRandomFields(xContentType, originalBytes, excludeFilter, random());
|
||||||
|
} else {
|
||||||
|
mutated = originalBytes;
|
||||||
|
}
|
||||||
UpdateResponse parsedUpdateResponse;
|
UpdateResponse parsedUpdateResponse;
|
||||||
try (XContentParser parser = createParser(xContentType.xContent(), updateResponseBytes)) {
|
try (XContentParser parser = createParser(xContentType.xContent(), mutated)) {
|
||||||
parsedUpdateResponse = UpdateResponse.fromXContent(parser);
|
parsedUpdateResponse = UpdateResponse.fromXContent(parser);
|
||||||
assertNull(parser.nextToken());
|
assertNull(parser.nextToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can't use equals() to compare the original and the parsed delete response
|
IndexResponseTests.assertDocWriteResponse(expectedUpdateResponse, parsedUpdateResponse);
|
||||||
// because the random delete response can contain shard failures with exceptions,
|
if (addRandomFields == false) {
|
||||||
// and those exceptions are not parsed back with the same types.
|
assertEquals(expectedUpdateResponse.getGetResult(), parsedUpdateResponse.getGetResult());
|
||||||
assertUpdateResponse(expectedUpdateResponse, parsedUpdateResponse);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void assertUpdateResponse(UpdateResponse expected, UpdateResponse actual) {
|
// Prints out the parsed UpdateResponse object to verify that it is the same as the expected output.
|
||||||
IndexResponseTests.assertDocWriteResponse(expected, actual);
|
// If random fields have been inserted, it checks that they have been filtered out and that they do
|
||||||
assertEquals(expected.getGetResult(), actual.getGetResult());
|
// not alter the final output of the parsed object.
|
||||||
|
BytesReference parsedBytes = toXContent(parsedUpdateResponse, xContentType, humanReadable);
|
||||||
|
BytesReference expectedBytes = toXContent(expectedUpdateResponse, xContentType, humanReadable);
|
||||||
|
assertToXContentEquivalent(expectedBytes, parsedBytes, xContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue