Skip to content

Commit 61a8bba

Browse files
authored
Add support for protocol test error cases + add cbor error cases (#6207)
* Add support for protocol test error cases + add cbor error cases * Fix checkstyle
1 parent cf5a640 commit 61a8bba

File tree

10 files changed

+252
-2
lines changed

10 files changed

+252
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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.unmarshalling;
17+
18+
19+
import static org.junit.Assert.fail;
20+
import static org.unitils.reflectionassert.ReflectionAssert.assertReflectionEquals;
21+
22+
import com.fasterxml.jackson.databind.JsonNode;
23+
import java.lang.reflect.Field;
24+
import org.junit.Assert;
25+
import software.amazon.awssdk.core.exception.SdkServiceException;
26+
import software.amazon.awssdk.protocol.reflect.ShapeModelReflector;
27+
28+
public class UnmarshalledErrorAssertion extends UnmarshallingAssertion {
29+
private final JsonNode expectedError;
30+
31+
public UnmarshalledErrorAssertion(JsonNode expectedError) {
32+
this.expectedError = expectedError;
33+
}
34+
35+
@Override
36+
protected void doAssert(UnmarshallingTestContext context, Object actual) throws Exception {
37+
if (!(actual instanceof SdkServiceException)) {
38+
fail("Expected unmarshalled object to be an instance of SdkServiceException");
39+
}
40+
SdkServiceException actualException = (SdkServiceException) actual;
41+
SdkServiceException expectedException = createExpectedResult(context);
42+
for (Field field : expectedException.getClass().getDeclaredFields()) {
43+
assertFieldEquals(field, actualException, expectedException);
44+
}
45+
46+
if (expectedException.getMessage() != null) {
47+
Assert.assertTrue(actualException.getMessage().startsWith(expectedException.getMessage()));
48+
}
49+
}
50+
51+
private SdkServiceException createExpectedResult(UnmarshallingTestContext context) {
52+
return (SdkServiceException) new ShapeModelReflector(context.getModel(), context.getErrorName() + "Exception",
53+
this.expectedError).createShapeObject();
54+
}
55+
56+
private void assertFieldEquals(Field field, Object actual, Object expectedResult) throws
57+
Exception {
58+
field.setAccessible(true);
59+
assertReflectionEquals(field.get(expectedResult), field.get(actual));
60+
}
61+
}

test/protocol-tests-core/src/main/java/software/amazon/awssdk/protocol/asserts/unmarshalling/UnmarshallingTestContext.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public class UnmarshallingTestContext {
2626
private IntermediateModel model;
2727
private String operationName;
2828
private String streamedResponse;
29+
private String errorName;
2930

3031
public UnmarshallingTestContext withModel(IntermediateModel model) {
3132
this.model = model;
@@ -58,4 +59,12 @@ public String getStreamedResponse() {
5859
return streamedResponse;
5960
}
6061

62+
public UnmarshallingTestContext withErrorName(String errorName) {
63+
this.errorName = errorName;
64+
return this;
65+
}
66+
67+
public String getErrorName() {
68+
return errorName;
69+
}
6170
}

test/protocol-tests-core/src/main/java/software/amazon/awssdk/protocol/model/Then.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,22 @@
2020
import com.fasterxml.jackson.databind.JsonNode;
2121
import software.amazon.awssdk.protocol.asserts.marshalling.MarshallingAssertion;
2222
import software.amazon.awssdk.protocol.asserts.marshalling.SerializedAs;
23+
import software.amazon.awssdk.protocol.asserts.unmarshalling.UnmarshalledErrorAssertion;
2324
import software.amazon.awssdk.protocol.asserts.unmarshalling.UnmarshalledResultAssertion;
2425
import software.amazon.awssdk.protocol.asserts.unmarshalling.UnmarshallingAssertion;
2526

2627
public class Then {
2728

2829
private final MarshallingAssertion serializedAs;
2930
private final UnmarshallingAssertion deserializedAs;
31+
private final UnmarshallingAssertion errorDeserializedAs;
3032

3133
@JsonCreator
3234
public Then(@JsonProperty("serializedAs") SerializedAs serializedAs,
3335
@JsonProperty("deserializedAs") JsonNode deserializedAs) {
3436
this.serializedAs = serializedAs;
3537
this.deserializedAs = new UnmarshalledResultAssertion(deserializedAs);
38+
this.errorDeserializedAs = new UnmarshalledErrorAssertion(deserializedAs);
3639
}
3740

3841
/**
@@ -49,4 +52,11 @@ public UnmarshallingAssertion getUnmarshallingAssertion() {
4952
return deserializedAs;
5053
}
5154

55+
/**
56+
*
57+
* @return The assertion object to use for error unmarshalling tests
58+
*/
59+
public UnmarshallingAssertion getErrorUnmarshallingAssertion() {
60+
return errorDeserializedAs;
61+
}
5262
}

test/protocol-tests-core/src/main/java/software/amazon/awssdk/protocol/model/When.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ public class When {
2626
@JsonProperty(value = "operation")
2727
private String operationName;
2828

29+
@JsonProperty(value = "error")
30+
private String errorName;
31+
2932
public WhenAction getAction() {
3033
return action;
3134
}
@@ -41,4 +44,12 @@ public String getOperationName() {
4144
public void setOperationName(String operationName) {
4245
this.operationName = operationName;
4346
}
47+
48+
public void setErrorName(String errorName) {
49+
this.errorName = errorName;
50+
}
51+
52+
public String getErrorName() {
53+
return errorName;
54+
}
4455
}

test/protocol-tests-core/src/main/java/software/amazon/awssdk/protocol/model/WhenAction.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717

1818
public enum WhenAction {
1919
MARSHALL("marshall"),
20-
UNMARSHALL("unmarshall");
20+
UNMARSHALL("unmarshall"),
21+
ERROR_UNMARSHALL("errorUnmarshall");
2122

2223
private final String action;
2324

@@ -31,6 +32,8 @@ public static WhenAction fromValue(String action) {
3132
return MARSHALL;
3233
case "unmarshall":
3334
return UNMARSHALL;
35+
case "errorUnmarshall":
36+
return ERROR_UNMARSHALL;
3437
default:
3538
throw new IllegalArgumentException("Unsupported test action " + action);
3639
}

test/protocol-tests-core/src/main/java/software/amazon/awssdk/protocol/reflect/ShapeModelReflector.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ private void initializeFields(ShapeModel structureShape, JsonNode input,
110110
Iterator<String> fieldNames = input.fieldNames();
111111
while (fieldNames.hasNext()) {
112112
String memberName = fieldNames.next();
113+
// error structures have special case handling of "message"
114+
if (structureShape.getErrorCode() != null && memberName.equalsIgnoreCase("message")) {
115+
Method setter = shapeObject.getClass().getMethod("message", String.class);
116+
setter.setAccessible(true);
117+
setter.invoke(shapeObject, input.get(memberName).asText());
118+
continue;
119+
}
120+
113121
MemberModel memberModel = structureShape.getMemberByC2jName(memberName);
114122
if (memberModel == null) {
115123
throw new IllegalArgumentException("Member " + memberName + " was not found in the " +

test/protocol-tests-core/src/main/java/software/amazon/awssdk/protocol/runners/ProtocolTestRunner.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public void runTest(TestCase testCase) throws Exception {
7272
marshallingTestRunner.runTest(testCase);
7373
break;
7474
case UNMARSHALL:
75+
case ERROR_UNMARSHALL:
7576
unmarshallingTestRunner.runTest(testCase);
7677
break;
7778
default:

test/protocol-tests-core/src/main/java/software/amazon/awssdk/protocol/runners/UnmarshallingTestRunner.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.fasterxml.jackson.databind.JsonNode;
2424
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
2525
import com.github.tomakehurst.wiremock.client.WireMock;
26+
import java.lang.reflect.InvocationTargetException;
2627
import java.util.Base64;
2728
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
2829
import software.amazon.awssdk.codegen.model.intermediate.Metadata;
@@ -52,6 +53,21 @@ class UnmarshallingTestRunner {
5253

5354
void runTest(TestCase testCase) throws Exception {
5455
resetWireMock(testCase.getGiven().getResponse());
56+
57+
switch (testCase.getWhen().getAction()) {
58+
case UNMARSHALL:
59+
runUnmarshallTest(testCase);
60+
break;
61+
case ERROR_UNMARSHALL:
62+
runErrorUnmarshallTest(testCase);
63+
break;
64+
default:
65+
throw new IllegalArgumentException("UnmarshallingTestRunner unable to run test case for action "
66+
+ testCase.getWhen().getAction());
67+
}
68+
}
69+
70+
private void runUnmarshallTest(TestCase testCase) throws Exception {
5571
String operationName = testCase.getWhen().getOperationName();
5672
ShapeModelReflector shapeModelReflector = createShapeModelReflector(testCase);
5773
if (!hasStreamingMember(operationName)) {
@@ -60,12 +76,32 @@ void runTest(TestCase testCase) throws Exception {
6076
} else {
6177
CapturingResponseTransformer responseHandler = new CapturingResponseTransformer();
6278
Object actualResult = clientReflector
63-
.invokeStreamingMethod(testCase, shapeModelReflector.createShapeObject(), responseHandler);
79+
.invokeStreamingMethod(testCase, shapeModelReflector.createShapeObject(), responseHandler);
6480
testCase.getThen().getUnmarshallingAssertion()
6581
.assertMatches(createContext(operationName, responseHandler.captured), actualResult);
6682
}
6783
}
6884

85+
private void runErrorUnmarshallTest(TestCase testCase) throws Exception {
86+
String operationName = testCase.getWhen().getOperationName();
87+
ShapeModelReflector shapeModelReflector = createShapeModelReflector(testCase);
88+
try {
89+
clientReflector.invokeMethod(testCase, shapeModelReflector.createShapeObject());
90+
throw new IllegalStateException("Test case expected client to throw error");
91+
} catch (InvocationTargetException t) {
92+
String errorName = testCase.getWhen().getErrorName();
93+
testCase.getThen().getErrorUnmarshallingAssertion().assertMatches(
94+
createErrorContext(operationName, errorName), t.getCause());
95+
}
96+
}
97+
98+
private UnmarshallingTestContext createErrorContext(String operationName, String errorName) {
99+
return new UnmarshallingTestContext()
100+
.withModel(model)
101+
.withOperationName(operationName)
102+
.withErrorName(errorName);
103+
}
104+
69105
/**
70106
* {@link ResponseTransformer} that simply captures all the content as a String so we
71107
* can compare it with the expected in

test/protocol-tests-core/src/main/resources/software/amazon/awssdk/protocol/suites/cases/smithy-rpcv2-output.json

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,5 +663,76 @@
663663
"then": {
664664
"deserializedAs": {}
665665
}
666+
},
667+
{
668+
"description": "Parses simple RpcV2 Cbor errors.",
669+
"given": {
670+
"response": {
671+
"status_code": 400,
672+
"headers": {
673+
"smithy-protocol": "rpc-v2-cbor",
674+
"Content-Type": "application/cbor"
675+
},
676+
"binaryBody": "v2ZfX3R5cGV4LnNtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNJbnZhbGlkR3JlZXRpbmdnTWVzc2FnZWJIaf8="
677+
}
678+
},
679+
"when": {
680+
"action": "errorUnmarshall",
681+
"operation": "GreetingWithErrors",
682+
"error": "InvalidGreeting"
683+
},
684+
"then": {
685+
"deserializedAs": {
686+
"Message": "Hi"
687+
}
688+
}
689+
},
690+
{
691+
"description": "Parses a complex error with no message member",
692+
"given": {
693+
"response": {
694+
"status_code": 400,
695+
"headers": {
696+
"smithy-protocol": "rpc-v2-cbor",
697+
"Content-Type": "application/cbor"
698+
},
699+
"binaryBody": "v2ZfX3R5cGV4K3NtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNDb21wbGV4RXJyb3JoVG9wTGV2ZWxpVG9wIGxldmVsZk5lc3RlZL9jRm9vY2Jhcv//"
700+
}
701+
},
702+
"when": {
703+
"action": "errorUnmarshall",
704+
"operation": "GreetingWithErrors",
705+
"error": "ComplexError"
706+
},
707+
"then": {
708+
"deserializedAs": {
709+
"TopLevel": "Top level",
710+
"Nested": {
711+
"Foo": "bar"
712+
}
713+
}
714+
}
715+
},
716+
{
717+
"description": "Parses an empty complex error",
718+
"given": {
719+
"response": {
720+
"status_code": 400,
721+
"headers": {
722+
"smithy-protocol": "rpc-v2-cbor",
723+
"Content-Type": "application/cbor"
724+
},
725+
"binaryBody": "v2ZfX3R5cGV4K3NtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNDb21wbGV4RXJyb3L/"
726+
}
727+
},
728+
"when": {
729+
"action": "errorUnmarshall",
730+
"operation": "GreetingWithErrors",
731+
"error": "ComplexError"
732+
},
733+
"then": {
734+
"deserializedAs": {
735+
}
736+
}
666737
}
667738
]

test/protocol-tests/src/main/resources/codegen-resources/sdkrpcv2/service-2.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,19 @@
9595
"method": "POST",
9696
"requestUri": "/"
9797
}
98+
},
99+
"GreetingWithErrors":{
100+
"name":"GreetingWithErrors",
101+
"http":{
102+
"method":"POST",
103+
"requestUri":"/"
104+
},
105+
"output":{"shape":"GreetingWithErrorsOutput"},
106+
"errors":[
107+
{"shape":"ComplexError"},
108+
{"shape":"InvalidGreeting"}
109+
],
110+
"idempotent":true
98111
}
99112
},
100113
"shapes": {
@@ -679,6 +692,33 @@
679692
"shape": "AllTypesUnionStructure"
680693
}
681694
}
695+
},
696+
"GreetingWithErrorsOutput":{
697+
"type":"structure",
698+
"members":{
699+
"greeting":{"shape":"String"}
700+
}
701+
},
702+
"ComplexError":{
703+
"type":"structure",
704+
"members":{
705+
"TopLevel":{"shape":"String"},
706+
"Nested":{"shape":"ComplexNestedErrorData"}
707+
},
708+
"exception":true
709+
},
710+
"ComplexNestedErrorData":{
711+
"type":"structure",
712+
"members":{
713+
"Foo":{"shape":"String"}
714+
}
715+
},
716+
"InvalidGreeting":{
717+
"type":"structure",
718+
"members":{
719+
"Message":{"shape":"String"}
720+
},
721+
"exception":true
682722
}
683723
}
684724
}

0 commit comments

Comments
 (0)