Skip to content

Commit e97b94b

Browse files
authored
Expand protocol test coverage for json1.0, query and cbor (#6245)
* Add support for new smithy translated protocol test format * Various fixes + start adding new query tests * WIP - adding more new test features * All query tests passing! * Add json1.0 tests + fix test with custom host * Add cbor tests from smithy + add new cborEquals body assertion * Remove cbor test that java cannot change behavior for * Add changelog + fix checkstyle * Fix "/" on query paths * Fix request nil check * Revert behavior change in query when request path is set - remove failing protocol tests - avoid behavior changes in this PR * Minor cleanups * Add comment to explain content-type in header contains
1 parent 273187a commit e97b94b

36 files changed

+6285
-790
lines changed

codegen/src/main/java/software/amazon/awssdk/codegen/model/service/XmlNamespace.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,16 @@
1818
public class XmlNamespace {
1919

2020
private String prefix;
21-
2221
private String uri;
2322

23+
public XmlNamespace() {
24+
25+
}
26+
27+
public XmlNamespace(String uri) {
28+
this.uri = uri;
29+
}
30+
2431
public String getPrefix() {
2532
return prefix;
2633
}

codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,11 @@ public String getEnumValueName(String enumValue) {
302302
// Special cases
303303
result = result.replace("textORcsv", "TEXT_OR_CSV");
304304

305+
// leading digits, add a prefix
306+
if (result.matches("^\\d.*")) {
307+
result = "VALUE_" + result;
308+
}
309+
305310
// Split into words
306311
result = String.join("_", splitOnWordBoundaries(result));
307312

test/protocol-tests-core/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@
137137
<groupId>com.fasterxml.jackson.core</groupId>
138138
<artifactId>jackson-annotations</artifactId>
139139
</dependency>
140+
<dependency>
141+
<groupId>com.fasterxml.jackson.dataformat</groupId>
142+
<artifactId>jackson-dataformat-cbor</artifactId>
143+
</dependency>
140144
</dependencies>
141145

142146
<!-- Disable spotbugs for this test module to speed up the build. -->
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.protocol.asserts.marshalling;
17+
18+
import static org.junit.Assert.assertEquals;
19+
20+
import com.fasterxml.jackson.databind.JsonNode;
21+
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import com.fasterxml.jackson.databind.node.ArrayNode;
23+
import com.fasterxml.jackson.databind.node.DoubleNode;
24+
import com.fasterxml.jackson.databind.node.ObjectNode;
25+
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
26+
import com.github.tomakehurst.wiremock.verification.LoggedRequest;
27+
import java.util.Base64;
28+
29+
/**
30+
* Asserts on the body (expected to be CBOR) of the marshalled request.
31+
*/
32+
public class CborBodyAssertion extends MarshallingAssertion {
33+
private static final ObjectMapper MAPPER = new ObjectMapper(new CBORFactory());
34+
35+
private final String cborEquals;
36+
37+
public CborBodyAssertion(String cborEquals) {
38+
this.cborEquals = cborEquals;
39+
}
40+
41+
@Override
42+
protected void doAssert(LoggedRequest actual) throws Exception {
43+
JsonNode expected = normalizeToDoubles(MAPPER.readTree(Base64.getDecoder().decode(cborEquals)));
44+
JsonNode actualJson = normalizeToDoubles(MAPPER.readTree(actual.getBody()));
45+
assertEquals(expected, actualJson);
46+
}
47+
48+
/**
49+
* CBOR allows serializing floating point numbers to different sizes (eg, float16/32/64).
50+
* However, in assertEquals float and double nodes will never be equal so we convert them
51+
* all to doubles.
52+
*/
53+
private JsonNode normalizeToDoubles(JsonNode node) {
54+
if (node.isFloat() || node.isDouble()) {
55+
return DoubleNode.valueOf(node.doubleValue());
56+
}
57+
if (node.isArray()) {
58+
ArrayNode array = MAPPER.createArrayNode();
59+
for (JsonNode item : node) {
60+
array.add(normalizeToDoubles(item));
61+
}
62+
return array;
63+
}
64+
if (node.isObject()) {
65+
ObjectNode obj = MAPPER.createObjectNode();
66+
node.fields().forEachRemaining(entry -> obj.set(entry.getKey(), normalizeToDoubles(entry.getValue())));
67+
return obj;
68+
}
69+
return node;
70+
}
71+
}

test/protocol-tests-core/src/main/java/software/amazon/awssdk/protocol/asserts/marshalling/HeadersAssertion.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.github.tomakehurst.wiremock.http.HttpHeaders;
2323
import com.github.tomakehurst.wiremock.verification.LoggedRequest;
24+
import java.util.Collections;
2425
import java.util.List;
2526
import java.util.Map;
2627

@@ -32,6 +33,7 @@ public class HeadersAssertion extends MarshallingAssertion {
3233
private Map<String, List<String>> contains;
3334

3435
private List<String> doesNotContain;
36+
private List<String> mustContain;
3537

3638
public void setContains(Map<String, List<String>> contains) {
3739
this.contains = contains;
@@ -41,6 +43,10 @@ public void setDoesNotContain(List<String> doesNotContain) {
4143
this.doesNotContain = doesNotContain;
4244
}
4345

46+
public void setMustContain(List<String> mustContain) {
47+
this.mustContain = mustContain;
48+
}
49+
4450
@Override
4551
protected void doAssert(LoggedRequest actual) throws Exception {
4652
if (contains != null) {
@@ -49,13 +55,20 @@ protected void doAssert(LoggedRequest actual) throws Exception {
4955
if (doesNotContain != null) {
5056
assertDoesNotContainHeaders(actual.getHeaders());
5157
}
58+
if (mustContain != null) {
59+
assertMustContainHeaders(actual.getHeaders());
60+
}
5261
}
5362

5463
private void assertHeadersContains(HttpHeaders actual) {
5564
contains.forEach((expectedKey, expectedValues) -> {
5665
assertTrue(String.format("Header '%s' was expected to be present. Actual headers: %s", expectedKey, actual),
5766
actual.getHeader(expectedKey).isPresent());
5867
List<String> actualValues = actual.getHeader(expectedKey).values();
68+
// the Java SDK adds charset to content-type. This is valid but not included in the protocol tests
69+
if (expectedKey.equalsIgnoreCase("Content-Type") && actualValues.size() == 1) {
70+
actualValues = Collections.singletonList(actualValues.get(0).replace("; charset=UTF-8", ""));
71+
}
5972
assertEquals(expectedValues, actualValues);
6073
});
6174
}
@@ -66,4 +79,11 @@ private void assertDoesNotContainHeaders(HttpHeaders actual) {
6679
actual.getHeader(headerName).isPresent());
6780
});
6881
}
82+
83+
private void assertMustContainHeaders(HttpHeaders actual) {
84+
mustContain.forEach(headerName -> {
85+
assertTrue(String.format("Header '%s' was expected to be present", headerName),
86+
actual.getHeader(headerName).isPresent());
87+
});
88+
}
6989
}

test/protocol-tests-core/src/main/java/software/amazon/awssdk/protocol/asserts/marshalling/JsonBodyAssertion.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919

2020
import com.fasterxml.jackson.databind.JsonNode;
2121
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import com.fasterxml.jackson.databind.node.ArrayNode;
23+
import com.fasterxml.jackson.databind.node.IntNode;
24+
import com.fasterxml.jackson.databind.node.LongNode;
25+
import com.fasterxml.jackson.databind.node.ObjectNode;
2226
import com.github.tomakehurst.wiremock.verification.LoggedRequest;
2327

2428
/**
@@ -37,8 +41,39 @@ public JsonBodyAssertion(String jsonEquals) {
3741
@Override
3842
protected void doAssert(LoggedRequest actual) throws Exception {
3943
JsonNode expected = MAPPER.readTree(jsonEquals);
40-
JsonNode actualJson = MAPPER.readTree(actual.getBodyAsString());
44+
JsonNode actualJson = convertWholeNumberDoubleToLong(MAPPER.readTree(actual.getBodyAsString()));
4145
assertEquals(expected, actualJson);
4246
}
4347

48+
/**
49+
* We serialize some numbers (in particular epoch timestamps) as doubles such as 123.000.
50+
* In protocol tests, these values are parsed as longs. This conversion insures that
51+
* 123.000 will equal 123.
52+
*/
53+
public static JsonNode convertWholeNumberDoubleToLong(JsonNode node) {
54+
if (node.isDouble()) {
55+
double value = node.doubleValue();
56+
if (value % 1 == 0) {
57+
if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
58+
return new IntNode((int) value);
59+
}
60+
return new LongNode((long) value);
61+
}
62+
}
63+
if (node.isObject()) {
64+
ObjectNode obj = (ObjectNode) node;
65+
ObjectNode result = obj.objectNode();
66+
obj.fieldNames().forEachRemaining(field -> result.set(field, convertWholeNumberDoubleToLong(obj.get(field))));
67+
return result;
68+
}
69+
if (node.isArray()) {
70+
ArrayNode array = (ArrayNode) node;
71+
ArrayNode result = array.arrayNode();
72+
for (JsonNode item : array) {
73+
result.add(convertWholeNumberDoubleToLong(item));
74+
}
75+
return result;
76+
}
77+
return node;
78+
}
4479
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.protocol.asserts.marshalling;
17+
18+
import static org.hamcrest.Matchers.containsInAnyOrder;
19+
import static org.hamcrest.Matchers.equalTo;
20+
import static org.junit.Assert.assertThat;
21+
import static software.amazon.awssdk.protocol.asserts.marshalling.QueryUtils.parseQueryParams;
22+
import static software.amazon.awssdk.protocol.asserts.marshalling.QueryUtils.parseQueryParamsFromBody;
23+
24+
import com.github.tomakehurst.wiremock.verification.LoggedRequest;
25+
import java.util.List;
26+
import java.util.Map;
27+
import java.util.stream.Collectors;
28+
29+
public class QueryBodyAssertion extends MarshallingAssertion {
30+
private final String queryEquals;
31+
32+
public QueryBodyAssertion(String queryEquals) {
33+
this.queryEquals = queryEquals;
34+
}
35+
36+
@Override
37+
protected void doAssert(LoggedRequest actual) throws Exception {
38+
Map<String, List<String>> expectedParams = parseQueryParamsFromBody(queryEquals);
39+
try {
40+
Map<String, List<String>> actualParams = parseQueryParams(actual);
41+
doAssert(expectedParams, actualParams);
42+
} catch (AssertionError error) {
43+
// We may send the query params in the body if there is no other content. Try
44+
// decoding body as params and rerun the assertions.
45+
Map<String, List<String>> actualParams = parseQueryParamsFromBody(
46+
actual.getBodyAsString());
47+
doAssert(expectedParams, actualParams);
48+
}
49+
}
50+
51+
private void doAssert(Map<String, List<String>> expectedParams, Map<String, List<String>> actualParams) {
52+
assertThat(actualParams.keySet(), equalTo(expectedParams.keySet()));
53+
expectedParams.forEach((key, value) -> assertParamsEqual(actualParams.get(key), value));
54+
}
55+
56+
private void assertParamsEqual(List<String> actual, List<String> expected) {
57+
if (expected.stream().allMatch(QueryBodyAssertion::isNumeric)) {
58+
assertThat(
59+
actual.stream().map(Double::parseDouble).collect(Collectors.toList()),
60+
containsInAnyOrder(expected.stream().map(Double::parseDouble).toArray()));
61+
} else {
62+
assertThat(actual, containsInAnyOrder(expected.toArray()));
63+
}
64+
}
65+
66+
public static boolean isNumeric(String str) {
67+
if (str == null || str.isEmpty()) {
68+
return false;
69+
}
70+
try {
71+
Double.parseDouble(str);
72+
return true;
73+
} catch (NumberFormatException e) {
74+
return false;
75+
}
76+
}
77+
78+
79+
}

0 commit comments

Comments
 (0)