From b8bd230061aa995d43a0f50afae9acf679318fe9 Mon Sep 17 00:00:00 2001 From: jianglai Date: Thu, 2 Aug 2018 08:46:07 -0700 Subject: [PATCH] Add tests for web whois protocols module The web whois protocols are basically HTTP(S) server protocols. Reuse the same test structure for HTTP client protocols. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=207106520 --- .../registry/proxy/ProtocolModuleTest.java | 11 ++ .../google/registry/proxy/TestUtils.java | 16 ++- .../proxy/WebWhoisProtocolsModuleTest.java | 109 ++++++++++++++++++ 3 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 javatests/google/registry/proxy/WebWhoisProtocolsModuleTest.java diff --git a/javatests/google/registry/proxy/ProtocolModuleTest.java b/javatests/google/registry/proxy/ProtocolModuleTest.java index 03a40ec62..dcc2630b0 100644 --- a/javatests/google/registry/proxy/ProtocolModuleTest.java +++ b/javatests/google/registry/proxy/ProtocolModuleTest.java @@ -29,6 +29,7 @@ import google.registry.proxy.EppProtocolModule.EppProtocol; import google.registry.proxy.HealthCheckProtocolModule.HealthCheckProtocol; import google.registry.proxy.HttpsRelayProtocolModule.HttpsRelayProtocol; import google.registry.proxy.ProxyConfig.Environment; +import google.registry.proxy.WebWhoisProtocolsModule.HttpWhoisProtocol; import google.registry.proxy.WhoisProtocolModule.WhoisProtocol; import google.registry.proxy.handler.BackendMetricsHandler; import google.registry.proxy.handler.ProxyProtocolHandler; @@ -38,6 +39,7 @@ import google.registry.proxy.handler.RelayHandler.FullHttpRequestRelayHandler; import google.registry.proxy.handler.RelayHandler.FullHttpResponseRelayHandler; import google.registry.proxy.handler.SslClientInitializer; import google.registry.proxy.handler.SslServerInitializer; +import google.registry.proxy.handler.WebWhoisRedirectHandler; import google.registry.testing.FakeClock; import google.registry.util.Clock; import io.netty.channel.Channel; @@ -95,6 +97,11 @@ public abstract class ProtocolModuleTest { // tested separately in their respective unit tests. FullHttpRequestRelayHandler.class, FullHttpResponseRelayHandler.class, + // This handler is tested in its own unit tests. It is installed in web whois redirect + // protocols. The end-to-end tests for the rest of the handlers in its pipeline need to + // be able to emit incoming requests out of the channel for assertions. Therefore this + // handler is removed from the pipeline. + WebWhoisRedirectHandler.class, // The rest are not part of business logic and do not need to be tested, obviously. LoggingHandler.class, // Metrics instrumentation is tested separately. @@ -179,6 +186,7 @@ public abstract class ProtocolModuleTest { TestModule.class, CertificateModule.class, WhoisProtocolModule.class, + WebWhoisProtocolsModule.class, EppProtocolModule.class, HealthCheckProtocolModule.class, HttpsRelayProtocolModule.class @@ -195,6 +203,9 @@ public abstract class ProtocolModuleTest { @HttpsRelayProtocol ImmutableList> httpsRelayHandlers(); + + @HttpWhoisProtocol + ImmutableList> httpWhoisHandlers(); } /** diff --git a/javatests/google/registry/proxy/TestUtils.java b/javatests/google/registry/proxy/TestUtils.java index 31d44d0ad..c62e442dd 100644 --- a/javatests/google/registry/proxy/TestUtils.java +++ b/javatests/google/registry/proxy/TestUtils.java @@ -30,7 +30,9 @@ import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpMessage; import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.cookie.ClientCookieEncoder; @@ -66,6 +68,12 @@ public class TestUtils { return response; } + public static FullHttpResponse makeHttpResponse(HttpResponseStatus status) { + FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status); + response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, 0); + return response; + } + public static FullHttpRequest makeWhoisHttpRequest( String content, String host, String path, String accessToken) { FullHttpRequest request = makeHttpPostRequest(content, host, path); @@ -129,10 +137,12 @@ public class TestUtils { *

This method is not type-safe, msg1 & msg2 can be a request and a response, respectively. Do * not use this method directly. */ - private static void assertHttpMessageEquivalent(FullHttpMessage msg1, FullHttpMessage msg2) { + private static void assertHttpMessageEquivalent(HttpMessage msg1, HttpMessage msg2) { assertThat(msg1.protocolVersion()).isEqualTo(msg2.protocolVersion()); - assertThat(msg1.content()).isEqualTo(msg2.content()); assertThat(msg1.headers()).isEqualTo(msg2.headers()); + if (msg1 instanceof FullHttpRequest && msg2 instanceof FullHttpRequest) { + assertThat(((FullHttpRequest) msg1).content()).isEqualTo(((FullHttpRequest) msg2).content()); + } } public static void assertHttpResponseEquivalent(FullHttpResponse res1, FullHttpResponse res2) { @@ -140,7 +150,7 @@ public class TestUtils { assertHttpMessageEquivalent(res1, res2); } - public static void assertHttpRequestEquivalent(FullHttpRequest req1, FullHttpRequest req2) { + public static void assertHttpRequestEquivalent(HttpRequest req1, HttpRequest req2) { assertHttpMessageEquivalent(req1, req2); } } diff --git a/javatests/google/registry/proxy/WebWhoisProtocolsModuleTest.java b/javatests/google/registry/proxy/WebWhoisProtocolsModuleTest.java new file mode 100644 index 000000000..adb521d68 --- /dev/null +++ b/javatests/google/registry/proxy/WebWhoisProtocolsModuleTest.java @@ -0,0 +1,109 @@ +// Copyright 2018 The Nomulus Authors. All Rights Reserved. +// +// Licensed 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 google.registry.proxy; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.proxy.TestUtils.assertHttpRequestEquivalent; +import static google.registry.proxy.TestUtils.assertHttpResponseEquivalent; +import static google.registry.proxy.TestUtils.makeHttpGetRequest; +import static google.registry.proxy.TestUtils.makeHttpResponse; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.codec.http.DefaultHttpRequest; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * End-to-end tests for {@link WebWhoisProtocolsModule}. + * + *

This protocol defines a connection in which the proxy behaves as a standard http server (sans + * the redirect operation which is excluded in end-to-end testing). Because non user-defined + * handlers are used, the tests here focus on verifying that the request written to the network + * socket by a client is reconstructed faithfully by the server, and vice versa, that the response a + * client decoded from incoming bytes is equivalent to the response sent by the server. + * + *

These tests only ensure that the server represented by this protocol is compatible with a + * client implementation provided by Netty itself. They test the self-consistency of various Netty + * handlers that deal with HTTP protocol, but not whether the handlers converts between bytes and + * HTTP messages correctly, which is presumed correct. + * + *

Only the HTTP redirect protocol is tested as both protocols share the same handlers except for + * those that are excluded ({@code SslServerInitializer}, {@code WebWhoisRedirectHandler}). + */ +@RunWith(JUnit4.class) +public class WebWhoisProtocolsModuleTest extends ProtocolModuleTest { + + private static final String HOST = "test.tld"; + private static final String PATH = "/path/to/test"; + + private final EmbeddedChannel clientChannel = + new EmbeddedChannel(new HttpClientCodec(), new HttpObjectAggregator(512 * 1024)); + + public WebWhoisProtocolsModuleTest() { + super(TestComponent::httpWhoisHandlers); + } + + /** + * Tests that the client converts given {@link FullHttpRequest} to bytes, which is sent to the + * server and reconstructed to a {@link FullHttpRequest} that is equivalent to the original. Then + * test that the server converts given {@link FullHttpResponse} to bytes, which is sent to the + * client and reconstructed to a {@link FullHttpResponse} that is equivalent to the original. + * + *

The request and response equivalences are tested in the same method because the client codec + * tries to pair the response it receives with the request it sends. Receiving a response without + * sending a request first will cause the {@link HttpObjectAggregator} to fail to aggregate + * properly. + */ + private void requestAndRespondWithStatus(HttpResponseStatus status) { + ByteBuf buffer; + FullHttpRequest requestSent = makeHttpGetRequest(HOST, PATH); + // Need to send a copy as the content read index will advance after the request is written to + // the outbound of client channel, making comparison with requestReceived fail. + assertThat(clientChannel.writeOutbound(requestSent.copy())).isTrue(); + buffer = clientChannel.readOutbound(); + assertThat(channel.writeInbound(buffer)).isTrue(); + // We only have a DefaultHttpRequest, not a FullHttpRequest because there is no HTTP aggregator + // in the server's pipeline. But it is fine as we are not interested in the content (payload) of + // the request, just its headers, which are contained in the DefaultHttpRequest. + DefaultHttpRequest requestReceived = channel.readInbound(); + // Verify that the request received is the same as the request sent. + assertHttpRequestEquivalent(requestSent, requestReceived); + + FullHttpResponse responseSent = makeHttpResponse(status); + assertThat(channel.writeOutbound(responseSent.copy())).isTrue(); + buffer = channel.readOutbound(); + assertThat(clientChannel.writeInbound(buffer)).isTrue(); + FullHttpResponse responseReceived = clientChannel.readInbound(); + // Verify that the request received is the same as the request sent. + assertHttpResponseEquivalent(responseSent, responseReceived); + } + + @Test + public void testSuccess_OkResponse() { + requestAndRespondWithStatus(HttpResponseStatus.OK); + } + + @Test + public void testSuccess_NonOkResponse() { + requestAndRespondWithStatus(HttpResponseStatus.BAD_REQUEST); + } +}