Skip to content

Commit e4be78c

Browse files
authored
Add MockPlugin (#505)
The MockPlugin is used to intercept requests and return canned responses, shapes, or exceptions. Various tests that previously did something similar to this have been updated to use it. Other minor fixes were made to make this happen: 1. ClientTransport is not needed to be applied manually in Client. 2. ClientTransport is applied eagerly as a plugin when applied to the ClientConfig.Builder. 3. Changed a Boolean to boolean in UriTreeMatcherMapBuilder to avoid an NPE. 4. ClientProtocol now uses a ShapeId instead of a String. 5. Added a method to DynamicClient to create a SerializableStruct from the converted model. 6. Added a helper method to detect the correct HTTP status of an error. 7. Fixed a bug in the HttpBindingSerializer where the status code wasn't being properly set for errors (was always 200).
1 parent 52a3f0e commit e4be78c

File tree

32 files changed

+937
-162
lines changed

32 files changed

+937
-162
lines changed

aws/aws-client-http/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ dependencies {
1313
api(project(":http-api"))
1414

1515
testImplementation(project(":dynamic-client"))
16+
testImplementation(project(":mock-client-plugin"))
1617
testImplementation(project(":aws:client-awsjson"))
1718
}

aws/aws-client-http/src/test/java/software/amazon/smithy/java/aws/client/http/AmzSdkRequestPluginTest.java

Lines changed: 25 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,17 @@
66
package software.amazon.smithy.java.aws.client.http;
77

88
import static org.hamcrest.MatcherAssert.assertThat;
9-
import static org.hamcrest.Matchers.contains;
9+
import static org.hamcrest.Matchers.equalTo;
10+
import static org.hamcrest.Matchers.hasSize;
1011

1112
import java.time.Duration;
12-
import java.util.concurrent.CompletableFuture;
13-
import java.util.concurrent.atomic.AtomicInteger;
1413
import org.junit.jupiter.api.Test;
1514
import software.amazon.smithy.java.aws.client.awsjson.AwsJson1Protocol;
16-
import software.amazon.smithy.java.client.core.ClientTransport;
17-
import software.amazon.smithy.java.client.core.MessageExchange;
1815
import software.amazon.smithy.java.client.core.auth.scheme.AuthSchemeResolver;
1916
import software.amazon.smithy.java.client.core.endpoint.EndpointResolver;
20-
import software.amazon.smithy.java.client.core.interceptors.ClientInterceptor;
21-
import software.amazon.smithy.java.client.core.interceptors.RequestHook;
22-
import software.amazon.smithy.java.client.http.HttpMessageExchange;
23-
import software.amazon.smithy.java.context.Context;
17+
import software.amazon.smithy.java.client.http.mock.MockPlugin;
18+
import software.amazon.smithy.java.client.http.mock.MockQueue;
2419
import software.amazon.smithy.java.dynamicclient.DynamicClient;
25-
import software.amazon.smithy.java.http.api.HttpRequest;
2620
import software.amazon.smithy.java.http.api.HttpResponse;
2721
import software.amazon.smithy.java.io.datastream.DataStream;
2822
import software.amazon.smithy.java.retries.api.AcquireInitialTokenRequest;
@@ -61,54 +55,17 @@ private static final class Token implements RetryToken {}
6155

6256
@Test
6357
public void injectsHeader() {
64-
AtomicInteger attempt = new AtomicInteger(0);
58+
var mockQueue = new MockQueue();
59+
var mock = MockPlugin.builder().addQueue(mockQueue).build();
6560

6661
var client = DynamicClient.builder()
6762
.service(SERVICE)
6863
.model(MODEL)
6964
.protocol(new AwsJson1Protocol(SERVICE))
65+
.addPlugin(mock)
7066
.authSchemeResolver(AuthSchemeResolver.NO_AUTH)
71-
.transport(new ClientTransport<HttpRequest, HttpResponse>() {
72-
@Override
73-
public MessageExchange<HttpRequest, HttpResponse> messageExchange() {
74-
return HttpMessageExchange.INSTANCE;
75-
}
76-
77-
@Override
78-
public CompletableFuture<HttpResponse> send(Context context, HttpRequest request) {
79-
var i = attempt.incrementAndGet();
80-
if (i == 1) {
81-
return CompletableFuture.completedFuture(
82-
HttpResponse.builder()
83-
.statusCode(429)
84-
.body(DataStream.ofString("{\"__type\":\"InvalidSprocketId\"}"))
85-
.build()
86-
);
87-
} else if (i == 2) {
88-
return CompletableFuture.completedFuture(
89-
HttpResponse.builder()
90-
.statusCode(200)
91-
.body(DataStream.ofString("{\"id\":\"1\"}"))
92-
.build()
93-
);
94-
} else {
95-
throw new IllegalStateException("Unexpected attempt " + i);
96-
}
97-
}
98-
})
9967
.endpointResolver(EndpointResolver.staticEndpoint("https://foo.com"))
10068
.addPlugin(new AmzSdkRequestPlugin())
101-
.addInterceptor(new ClientInterceptor() {
102-
@Override
103-
public void readBeforeTransmit(RequestHook<?, ?, ?> hook) {
104-
var request = (HttpRequest) hook.request();
105-
var i = attempt.get();
106-
assertThat(
107-
request.headers().allValues("amz-sdk-request"),
108-
contains("attempt=" + (i + 1) + "; max=3")
109-
);
110-
}
111-
})
11269
.retryStrategy(new RetryStrategy() {
11370
@Override
11471
public AcquireInitialTokenResponse acquireInitialToken(AcquireInitialTokenRequest request) {
@@ -137,6 +94,24 @@ public Builder toBuilder() {
13794
})
13895
.build();
13996

97+
mockQueue.enqueue(
98+
HttpResponse.builder()
99+
.statusCode(429)
100+
.body(DataStream.ofString("{\"__type\":\"InvalidSprocketId\"}"))
101+
.build()
102+
);
103+
mockQueue.enqueue(
104+
HttpResponse.builder()
105+
.statusCode(200)
106+
.body(DataStream.ofString("{\"id\":\"1\"}"))
107+
.build()
108+
);
109+
140110
client.call("CreateSprocket");
111+
112+
var requests = mock.getRequests();
113+
assertThat(requests, hasSize(2));
114+
assertThat(requests.get(0).request().headers().firstValue("amz-sdk-request"), equalTo("attempt=1; max=3"));
115+
assertThat(requests.get(1).request().headers().firstValue("amz-sdk-request"), equalTo("attempt=2; max=3"));
141116
}
142117
}

aws/client-awsjson/src/main/java/software/amazon/smithy/java/aws/client/awsjson/AwsJsonProtocol.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ abstract sealed class AwsJsonProtocol extends HttpClientProtocol permits AwsJson
4040
* discriminator of documents that use relative shape IDs.
4141
*/
4242
public AwsJsonProtocol(ShapeId trait, ShapeId service) {
43-
super(trait.toString());
43+
super(trait);
4444
this.service = service;
4545
this.codec = JsonCodec.builder().defaultNamespace(service.getNamespace()).build();
4646

aws/client-restjson/src/main/java/software/amazon/smithy/java/aws/client/restjson/RestJsonClientProtocol.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public final class RestJsonClientProtocol extends HttpBindingClientProtocol<AwsE
3939
* relative shape IDs.
4040
*/
4141
public RestJsonClientProtocol(ShapeId service) {
42-
super(RestJson1Trait.ID.toString());
42+
super(RestJson1Trait.ID);
4343

4444
this.codec = JsonCodec.builder()
4545
.useJsonName(true)

aws/client-restxml/src/main/java/software/amazon/smithy/java/aws/client/restxml/RestXmlClientProtocol.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public final class RestXmlClientProtocol extends HttpBindingClientProtocol<AwsEv
3939
* relative shape IDs.
4040
*/
4141
public RestXmlClientProtocol(ShapeId service) {
42-
super(RestXmlTrait.ID.toString());
42+
super(RestXmlTrait.ID);
4343

4444
this.codec = XmlCodec.builder().build();
4545
this.errorDeserializer = HttpErrorDeserializer.builder()

aws/client-rpcv2-cbor-protocol/src/main/java/software/amazon/smithy/java/client/rpcv2/RpcV2CborProtocol.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public final class RpcV2CborProtocol extends HttpClientProtocol {
3939
private final HttpErrorDeserializer errorDeserializer;
4040

4141
public RpcV2CborProtocol(ShapeId service) {
42-
super(Rpcv2CborTrait.ID.toString());
42+
super(Rpcv2CborTrait.ID);
4343
this.service = service;
4444
this.errorDeserializer = HttpErrorDeserializer.builder()
4545
.codec(CBOR_CODEC)

client-core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ dependencies {
1616

1717
testImplementation(project(":dynamic-client"))
1818
testImplementation(project(":aws:client-restjson"))
19+
testImplementation(project(":mock-client-plugin"))
1920
}

client-core/src/main/java/software/amazon/smithy/java/client/core/Client.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ protected Client(Builder<?, ?> builder) {
4040

4141
// Resolve the transport and apply it as a plugin before user-defined plugins, allowing user-defined plugins
4242
// to supersede and functionality of plugins applied by transports.
43-
configBuilder.resolveTransport().applyPlugin(configBuilder.transport());
43+
configBuilder.resolveTransport();
4444

4545
for (ClientPlugin plugin : builder.plugins) {
4646
configBuilder.applyPlugin(plugin);

client-core/src/main/java/software/amazon/smithy/java/client/core/ClientConfig.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ public String retryScope() {
351351
*/
352352
public Builder transport(ClientTransport<?, ?> transport) {
353353
this.transport = transport;
354+
applyPlugin(transport);
354355
return this;
355356
}
356357

@@ -367,10 +368,6 @@ public Builder resolveTransport() {
367368
if (transport == null) {
368369
transport(discoverTransport(protocol));
369370
}
370-
// If the transport has not yet been applied as a plugin, apply it now.
371-
if (!appliedPlugins.contains(transport.getClass())) {
372-
applyPlugin(transport);
373-
}
374371
return this;
375372
}
376373

client-core/src/main/java/software/amazon/smithy/java/client/core/ClientProtocol.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import software.amazon.smithy.java.core.schema.ApiOperation;
1414
import software.amazon.smithy.java.core.schema.SerializableStruct;
1515
import software.amazon.smithy.java.core.serde.TypeRegistry;
16+
import software.amazon.smithy.model.shapes.ShapeId;
1617

1718
/**
1819
* Handles request and response serialization.
@@ -26,7 +27,7 @@ public interface ClientProtocol<RequestT, ResponseT> {
2627
*
2728
* @return the protocol ID.
2829
*/
29-
String id();
30+
ShapeId id();
3031

3132
/**
3233
* Get the message exchange.

0 commit comments

Comments
 (0)