diff --git a/java/google/registry/module/backend/BUILD b/java/google/registry/module/backend/BUILD index 8f7eb154b..692f05eff 100644 --- a/java/google/registry/module/backend/BUILD +++ b/java/google/registry/module/backend/BUILD @@ -13,6 +13,7 @@ java_library( "//java/com/google/common/base", "//java/com/google/common/collect", "//java/com/google/common/net", + "//third_party/java/appengine:appengine-api", "//third_party/java/bouncycastle", "//third_party/java/dagger", "//third_party/java/joda_time", diff --git a/java/google/registry/module/backend/BackendComponent.java b/java/google/registry/module/backend/BackendComponent.java index a89a447c9..120c81ef5 100644 --- a/java/google/registry/module/backend/BackendComponent.java +++ b/java/google/registry/module/backend/BackendComponent.java @@ -26,6 +26,7 @@ import google.registry.groups.GroupsModule; import google.registry.groups.GroupssettingsModule; import google.registry.keyring.api.DummyKeyringModule; import google.registry.keyring.api.KeyModule; +import google.registry.module.backend.BackendRequestComponent.BackendRequestComponentModule; import google.registry.monitoring.metrics.MetricReporter; import google.registry.monitoring.whitebox.StackdriverModule; import google.registry.rde.JSchModule; @@ -37,7 +38,7 @@ import google.registry.request.Modules.ModulesServiceModule; import google.registry.request.Modules.URLFetchServiceModule; import google.registry.request.Modules.UrlFetchTransportModule; import google.registry.request.Modules.UseAppIdentityCredentialForGoogleApisModule; -import google.registry.request.RequestModule; +import google.registry.request.Modules.UserServiceModule; import google.registry.util.SystemClock.SystemClockModule; import google.registry.util.SystemSleeper.SystemSleeperModule; import javax.inject.Singleton; @@ -47,6 +48,7 @@ import javax.inject.Singleton; @Component( modules = { AppIdentityCredentialModule.class, + BackendRequestComponentModule.class, BigqueryModule.class, ConfigModule.class, DatastoreServiceModule.class, @@ -68,9 +70,10 @@ import javax.inject.Singleton; URLFetchServiceModule.class, UrlFetchTransportModule.class, UseAppIdentityCredentialForGoogleApisModule.class, + UserServiceModule.class, VoidDnsWriterModule.class, }) interface BackendComponent { - BackendRequestComponent startRequest(RequestModule requestModule); + BackendRequestHandler requestHandler(); MetricReporter metricReporter(); } diff --git a/java/google/registry/module/backend/BackendRequestComponent.java b/java/google/registry/module/backend/BackendRequestComponent.java index d36ff4c28..0c8682c38 100644 --- a/java/google/registry/module/backend/BackendRequestComponent.java +++ b/java/google/registry/module/backend/BackendRequestComponent.java @@ -14,6 +14,7 @@ package google.registry.module.backend; +import dagger.Module; import dagger.Subcomponent; import google.registry.backup.BackupModule; import google.registry.backup.CommitLogCheckpointAction; @@ -56,6 +57,7 @@ import google.registry.rde.RdeReportAction; import google.registry.rde.RdeReporter; import google.registry.rde.RdeStagingAction; import google.registry.rde.RdeUploadAction; +import google.registry.request.RequestComponentBuilder; import google.registry.request.RequestModule; import google.registry.request.RequestScope; import google.registry.tmch.NordnUploadAction; @@ -120,4 +122,13 @@ interface BackendRequestComponent { TmchSmdrlAction tmchSmdrlAction(); UpdateSnapshotViewAction updateSnapshotViewAction(); VerifyEntityIntegrityAction verifyEntityIntegrityAction(); + + @Subcomponent.Builder + abstract class Builder implements RequestComponentBuilder { + @Override public abstract Builder requestModule(RequestModule requestModule); + @Override public abstract BackendRequestComponent build(); + } + + @Module(subcomponents = BackendRequestComponent.class) + class BackendRequestComponentModule {} } diff --git a/java/google/registry/module/backend/BackendRequestHandler.java b/java/google/registry/module/backend/BackendRequestHandler.java new file mode 100644 index 000000000..be92dc7ed --- /dev/null +++ b/java/google/registry/module/backend/BackendRequestHandler.java @@ -0,0 +1,31 @@ +// Copyright 2016 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.module.backend; + +import com.google.appengine.api.users.UserService; +import google.registry.request.RequestHandler; +import javax.inject.Inject; +import javax.inject.Provider; + +/** Request handler for the backend module. */ +public class BackendRequestHandler + extends RequestHandler { + + @Inject BackendRequestHandler( + Provider componentBuilderProvider, + UserService userService) { + super(componentBuilderProvider, userService); + } +} diff --git a/java/google/registry/module/backend/BackendServlet.java b/java/google/registry/module/backend/BackendServlet.java index b5360a3a6..bcf17d94a 100644 --- a/java/google/registry/module/backend/BackendServlet.java +++ b/java/google/registry/module/backend/BackendServlet.java @@ -15,8 +15,6 @@ package google.registry.module.backend; import google.registry.monitoring.metrics.MetricReporter; -import google.registry.request.RequestHandler; -import google.registry.request.RequestModule; import google.registry.util.FormattingLogger; import java.io.IOException; import java.security.Security; @@ -31,12 +29,10 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; public final class BackendServlet extends HttpServlet { private static final BackendComponent component = DaggerBackendComponent.create(); + private static final BackendRequestHandler requestHandler = component.requestHandler(); private static final MetricReporter metricReporter = component.metricReporter(); private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); - private static final RequestHandler requestHandler = - RequestHandler.create(BackendRequestComponent.class); - @Override public void init() { Security.addProvider(new BouncyCastleProvider()); @@ -61,6 +57,6 @@ public final class BackendServlet extends HttpServlet { @Override public void service(HttpServletRequest req, HttpServletResponse rsp) throws IOException { - requestHandler.handleRequest(req, rsp, component.startRequest(new RequestModule(req, rsp))); + requestHandler.handleRequest(req, rsp); } } diff --git a/java/google/registry/module/frontend/BUILD b/java/google/registry/module/frontend/BUILD index 587bb0c64..8b736bb2d 100644 --- a/java/google/registry/module/frontend/BUILD +++ b/java/google/registry/module/frontend/BUILD @@ -11,6 +11,7 @@ java_library( deps = [ "//java/com/google/common/base", "//java/com/google/common/collect", + "//third_party/java/appengine:appengine-api", "//third_party/java/bouncycastle", "//third_party/java/dagger", "//third_party/java/jsr305_annotations", diff --git a/java/google/registry/module/frontend/FrontendComponent.java b/java/google/registry/module/frontend/FrontendComponent.java index 0800fb2c2..45015bdc0 100644 --- a/java/google/registry/module/frontend/FrontendComponent.java +++ b/java/google/registry/module/frontend/FrontendComponent.java @@ -19,6 +19,7 @@ import google.registry.braintree.BraintreeModule; import google.registry.config.ConfigModule; import google.registry.keyring.api.DummyKeyringModule; import google.registry.keyring.api.KeyModule; +import google.registry.module.frontend.FrontendRequestComponent.FrontendRequestComponentModule; import google.registry.monitoring.metrics.MetricReporter; import google.registry.monitoring.whitebox.StackdriverModule; import google.registry.request.Modules.AppIdentityCredentialModule; @@ -27,7 +28,6 @@ import google.registry.request.Modules.ModulesServiceModule; import google.registry.request.Modules.UrlFetchTransportModule; import google.registry.request.Modules.UseAppIdentityCredentialForGoogleApisModule; import google.registry.request.Modules.UserServiceModule; -import google.registry.request.RequestModule; import google.registry.ui.ConsoleConfigModule; import google.registry.util.SystemClock.SystemClockModule; import javax.inject.Singleton; @@ -42,6 +42,7 @@ import javax.inject.Singleton; ConsoleConfigModule.class, DummyKeyringModule.class, FrontendMetricsModule.class, + FrontendRequestComponentModule.class, Jackson2Module.class, KeyModule.class, ModulesServiceModule.class, @@ -52,6 +53,6 @@ import javax.inject.Singleton; UserServiceModule.class, }) interface FrontendComponent { - FrontendRequestComponent startRequest(RequestModule requestModule); + FrontendRequestHandler requestHandler(); MetricReporter metricReporter(); } diff --git a/java/google/registry/module/frontend/FrontendRequestComponent.java b/java/google/registry/module/frontend/FrontendRequestComponent.java index 37b883c89..25716fb86 100644 --- a/java/google/registry/module/frontend/FrontendRequestComponent.java +++ b/java/google/registry/module/frontend/FrontendRequestComponent.java @@ -14,6 +14,7 @@ package google.registry.module.frontend; +import dagger.Module; import dagger.Subcomponent; import google.registry.dns.DnsModule; import google.registry.flows.CheckApiAction; @@ -33,6 +34,7 @@ import google.registry.rdap.RdapIpAction; import google.registry.rdap.RdapModule; import google.registry.rdap.RdapNameserverAction; import google.registry.rdap.RdapNameserverSearchAction; +import google.registry.request.RequestComponentBuilder; import google.registry.request.RequestModule; import google.registry.request.RequestScope; import google.registry.ui.server.registrar.ConsoleUiAction; @@ -77,4 +79,13 @@ interface FrontendRequestComponent { RdapNameserverSearchAction rdapNameserverSearchAction(); WhoisHttpServer whoisHttpServer(); WhoisServer whoisServer(); + + @Subcomponent.Builder + abstract class Builder implements RequestComponentBuilder { + @Override public abstract Builder requestModule(RequestModule requestModule); + @Override public abstract FrontendRequestComponent build(); + } + + @Module(subcomponents = FrontendRequestComponent.class) + class FrontendRequestComponentModule {} } diff --git a/java/google/registry/module/frontend/FrontendRequestHandler.java b/java/google/registry/module/frontend/FrontendRequestHandler.java new file mode 100644 index 000000000..03138719e --- /dev/null +++ b/java/google/registry/module/frontend/FrontendRequestHandler.java @@ -0,0 +1,31 @@ +// Copyright 2016 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.module.frontend; + +import com.google.appengine.api.users.UserService; +import google.registry.request.RequestHandler; +import javax.inject.Inject; +import javax.inject.Provider; + +/** Request handler for the frontend module. */ +public class FrontendRequestHandler + extends RequestHandler { + + @Inject FrontendRequestHandler( + Provider componentBuilderProvider, + UserService userService) { + super(componentBuilderProvider, userService); + } +} diff --git a/java/google/registry/module/frontend/FrontendServlet.java b/java/google/registry/module/frontend/FrontendServlet.java index a1e701b46..ea1bb5ae3 100644 --- a/java/google/registry/module/frontend/FrontendServlet.java +++ b/java/google/registry/module/frontend/FrontendServlet.java @@ -15,8 +15,6 @@ package google.registry.module.frontend; import google.registry.monitoring.metrics.MetricReporter; -import google.registry.request.RequestHandler; -import google.registry.request.RequestModule; import google.registry.util.FormattingLogger; import java.io.IOException; import java.security.Security; @@ -31,12 +29,10 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; public final class FrontendServlet extends HttpServlet { private static final FrontendComponent component = DaggerFrontendComponent.create(); + private static final FrontendRequestHandler requestHandler = component.requestHandler(); private static final MetricReporter metricReporter = component.metricReporter(); private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); - private static final RequestHandler requestHandler = - RequestHandler.create(FrontendRequestComponent.class); - @Override public void init() { Security.addProvider(new BouncyCastleProvider()); @@ -61,6 +57,6 @@ public final class FrontendServlet extends HttpServlet { @Override public void service(HttpServletRequest req, HttpServletResponse rsp) throws IOException { - requestHandler.handleRequest(req, rsp, component.startRequest(new RequestModule(req, rsp))); + requestHandler.handleRequest(req, rsp); } } diff --git a/java/google/registry/module/tools/BUILD b/java/google/registry/module/tools/BUILD index 909d68676..3b1577b7f 100644 --- a/java/google/registry/module/tools/BUILD +++ b/java/google/registry/module/tools/BUILD @@ -11,6 +11,7 @@ java_library( deps = [ "//java/com/google/common/base", "//java/com/google/common/collect", + "//third_party/java/appengine:appengine-api", "//third_party/java/bouncycastle", "//third_party/java/dagger", "//third_party/java/jsr305_annotations", diff --git a/java/google/registry/module/tools/ToolsComponent.java b/java/google/registry/module/tools/ToolsComponent.java index 8ffb5507f..7fd5c54d0 100644 --- a/java/google/registry/module/tools/ToolsComponent.java +++ b/java/google/registry/module/tools/ToolsComponent.java @@ -23,6 +23,7 @@ import google.registry.groups.GroupsModule; import google.registry.groups.GroupssettingsModule; import google.registry.keyring.api.DummyKeyringModule; import google.registry.keyring.api.KeyModule; +import google.registry.module.tools.ToolsRequestComponent.ToolsRequestComponentModule; import google.registry.request.Modules.AppIdentityCredentialModule; import google.registry.request.Modules.DatastoreServiceModule; import google.registry.request.Modules.GoogleCredentialModule; @@ -30,7 +31,7 @@ import google.registry.request.Modules.Jackson2Module; import google.registry.request.Modules.ModulesServiceModule; import google.registry.request.Modules.UrlFetchTransportModule; import google.registry.request.Modules.UseAppIdentityCredentialForGoogleApisModule; -import google.registry.request.RequestModule; +import google.registry.request.Modules.UserServiceModule; import google.registry.util.SystemClock.SystemClockModule; import google.registry.util.SystemSleeper.SystemSleeperModule; import javax.inject.Singleton; @@ -52,11 +53,13 @@ import javax.inject.Singleton; Jackson2Module.class, KeyModule.class, ModulesServiceModule.class, + ToolsRequestComponentModule.class, UrlFetchTransportModule.class, UseAppIdentityCredentialForGoogleApisModule.class, + UserServiceModule.class, SystemClockModule.class, SystemSleeperModule.class, }) interface ToolsComponent { - ToolsRequestComponent startRequest(RequestModule requestModule); + ToolsRequestHandler requestHandler(); } diff --git a/java/google/registry/module/tools/ToolsRequestComponent.java b/java/google/registry/module/tools/ToolsRequestComponent.java index 41d9686dc..735663b64 100644 --- a/java/google/registry/module/tools/ToolsRequestComponent.java +++ b/java/google/registry/module/tools/ToolsRequestComponent.java @@ -14,6 +14,7 @@ package google.registry.module.tools; +import dagger.Module; import dagger.Subcomponent; import google.registry.dns.DnsModule; import google.registry.export.PublishDetailReportAction; @@ -24,6 +25,7 @@ import google.registry.loadtest.LoadTestAction; import google.registry.loadtest.LoadTestModule; import google.registry.mapreduce.MapreduceModule; import google.registry.monitoring.whitebox.WhiteboxModule; +import google.registry.request.RequestComponentBuilder; import google.registry.request.RequestModule; import google.registry.request.RequestScope; import google.registry.tools.server.CreateGroupsAction; @@ -77,4 +79,13 @@ interface ToolsRequestComponent { ResaveAllEppResourcesAction resaveAllEppResourcesAction(); UpdatePremiumListAction updatePremiumListAction(); VerifyOteAction verifyOteAction(); + + @Subcomponent.Builder + abstract class Builder implements RequestComponentBuilder { + @Override public abstract Builder requestModule(RequestModule requestModule); + @Override public abstract ToolsRequestComponent build(); + } + + @Module(subcomponents = ToolsRequestComponent.class) + class ToolsRequestComponentModule {} } diff --git a/java/google/registry/module/tools/ToolsRequestHandler.java b/java/google/registry/module/tools/ToolsRequestHandler.java new file mode 100644 index 000000000..5aa3b9824 --- /dev/null +++ b/java/google/registry/module/tools/ToolsRequestHandler.java @@ -0,0 +1,31 @@ +// Copyright 2016 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.module.tools; + +import com.google.appengine.api.users.UserService; +import google.registry.request.RequestHandler; +import javax.inject.Inject; +import javax.inject.Provider; + +/** Request handler for the tools module. */ +public class ToolsRequestHandler + extends RequestHandler { + + @Inject ToolsRequestHandler( + Provider componentBuilderProvider, + UserService userService) { + super(componentBuilderProvider, userService); + } +} diff --git a/java/google/registry/module/tools/ToolsServlet.java b/java/google/registry/module/tools/ToolsServlet.java index 7468cae64..5339f4951 100644 --- a/java/google/registry/module/tools/ToolsServlet.java +++ b/java/google/registry/module/tools/ToolsServlet.java @@ -14,8 +14,6 @@ package google.registry.module.tools; -import google.registry.request.RequestHandler; -import google.registry.request.RequestModule; import java.io.IOException; import java.security.Security; import javax.servlet.http.HttpServlet; @@ -27,9 +25,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; public final class ToolsServlet extends HttpServlet { private static final ToolsComponent component = DaggerToolsComponent.create(); - - private static final RequestHandler requestHandler = - RequestHandler.create(ToolsRequestComponent.class); + private static final ToolsRequestHandler requestHandler = component.requestHandler(); @Override public void init() { @@ -38,6 +34,6 @@ public final class ToolsServlet extends HttpServlet { @Override public void service(HttpServletRequest req, HttpServletResponse rsp) throws IOException { - requestHandler.handleRequest(req, rsp, component.startRequest(new RequestModule(req, rsp))); + requestHandler.handleRequest(req, rsp); } } diff --git a/java/google/registry/request/RequestComponentBuilder.java b/java/google/registry/request/RequestComponentBuilder.java new file mode 100644 index 000000000..d857a6138 --- /dev/null +++ b/java/google/registry/request/RequestComponentBuilder.java @@ -0,0 +1,25 @@ +// Copyright 2016 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.request; + +/** + * Builder interface for request components. + * + * @see RequestHandler + */ +public interface RequestComponentBuilder> { + B requestModule(RequestModule requestModule); + C build(); +} diff --git a/java/google/registry/request/RequestHandler.java b/java/google/registry/request/RequestHandler.java index a31a56f7d..07c528d25 100644 --- a/java/google/registry/request/RequestHandler.java +++ b/java/google/registry/request/RequestHandler.java @@ -26,17 +26,18 @@ import static javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY; import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import com.google.appengine.api.users.UserService; -import com.google.appengine.api.users.UserServiceFactory; import com.google.common.base.Optional; import google.registry.util.FormattingLogger; -import google.registry.util.NonFinalForTesting; +import google.registry.util.TypeUtils.TypeInstantiator; import java.io.IOException; +import javax.annotation.Nullable; +import javax.inject.Provider; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.joda.time.Duration; /** - * Dagger request processor for Nomulus. + * Dagger-based request processor. * *

This class creates an HTTP request processor from a Dagger component. It routes requests from * your servlet to an {@link Action @Action} annotated handler class. @@ -64,37 +65,58 @@ import org.joda.time.Duration; * *

This class also enforces the {@link Action#requireLogin() requireLogin} setting. * - * @param component type + * @param request component type + * @param builder for the request component */ -public final class RequestHandler { +public class RequestHandler> { private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); private static final Duration XSRF_VALIDITY = Duration.standardDays(1); - @NonFinalForTesting - private static UserService userService = UserServiceFactory.getUserService(); - - /** Creates a new request processor based off your component methods. */ - public static RequestHandler create(Class component) { - return new RequestHandler<>(component, Router.create(component)); - } - private final Router router; - - private RequestHandler(Class component, Router router) { - checkNotNull(component); - this.router = router; - } + private final Provider requestComponentBuilderProvider; + private final UserService userService; /** - * Runs the appropriate action for a servlet request. + * Constructor for subclasses to create a new request handler for a specific request component. * - * @param component is an instance of the component type passed to {@link #create(Class)} + *

This operation will generate a routing map for the component's {@code @Action}-returning + * methods using reflection, which is moderately expensive, so a given servlet should construct a + * single {@code RequestHandler} and re-use it across requests. + * + * @param requestComponentBuilderProvider a Dagger {@code Provider} of builder instances that can + * be used to construct new instances of the request component (with the required + * request-derived modules provided by this class) + * @param userService an instance of the App Engine UserService API */ - public void handleRequest(HttpServletRequest req, HttpServletResponse rsp, C component) - throws IOException { - checkNotNull(component); + protected RequestHandler(Provider requestComponentBuilderProvider, UserService userService) { + this(null, requestComponentBuilderProvider, userService); + } + + /** Creates a new RequestHandler with an explicit component class for test purposes. */ + public static > RequestHandler createForTest( + Class component, Provider requestComponentBuilderProvider, UserService userService) { + return new RequestHandler<>( + checkNotNull(component), requestComponentBuilderProvider, userService); + } + + private RequestHandler( + @Nullable Class component, + Provider requestComponentBuilderProvider, + UserService userService) { + // If the component class isn't explicitly provided, infer it from the class's own typing. + // This is safe only for use by subclasses of RequestHandler where the generic parameter is + // preserved at runtime, so only expose that option via the protected constructor. + this.router = Router.create( + component != null ? component : new TypeInstantiator(getClass()){}.getExactType()); + this.requestComponentBuilderProvider = checkNotNull(requestComponentBuilderProvider); + this.userService = checkNotNull(userService); + } + + /** Runs the appropriate action for a servlet request. */ + public void handleRequest(HttpServletRequest req, HttpServletResponse rsp) throws IOException { + checkNotNull(req); checkNotNull(rsp); Action.Method method; try { @@ -130,6 +152,11 @@ public final class RequestHandler { rsp.sendError(SC_FORBIDDEN, "Invalid " + X_CSRF_TOKEN); return; } + // Build a new request component using any modules we've constructed by this point. + C component = requestComponentBuilderProvider.get() + .requestModule(new RequestModule(req, rsp)) + .build(); + // Apply the selected Route to the component to produce an Action instance, and run it. try { route.get().instantiator().apply(component).run(); if (route.get().action().automaticallyPrintOk()) { diff --git a/javatests/google/registry/request/RequestHandlerTest.java b/javatests/google/registry/request/RequestHandlerTest.java index cdbc45c9c..c7a7c14b5 100644 --- a/javatests/google/registry/request/RequestHandlerTest.java +++ b/javatests/google/registry/request/RequestHandlerTest.java @@ -28,6 +28,7 @@ import com.google.common.testing.NullPointerTester; import google.registry.request.HttpException.ServiceUnavailableException; import google.registry.testing.AppEngineRule; import google.registry.testing.InjectRule; +import google.registry.testing.Providers; import google.registry.testing.UserInfo; import java.io.PrintWriter; import java.io.StringWriter; @@ -125,6 +126,14 @@ public final class RequestHandlerTest { } } + /** Fake Builder for the fake component above to satisfy RequestHandler expectations. */ + public abstract static class Builder implements RequestComponentBuilder { + @Override + public Builder requestModule(RequestModule requestModule) { + return this; + } + } + @Mock private HttpServletRequest req; @@ -148,11 +157,21 @@ public final class RequestHandlerTest { private final Component component = new Component(); private final StringWriter httpOutput = new StringWriter(); - private final RequestHandler handler = RequestHandler.create(Component.class); + private RequestHandler handler; @Before public void before() throws Exception { - inject.setStaticField(RequestHandler.class, "userService", userService); + // Initialize here, not inline, so that we pick up the mocked UserService. + handler = RequestHandler.createForTest( + Component.class, + Providers.of(new Builder() { + @Override + public Component build() { + // Use a fake Builder that returns the single component instance that uses the mocks. + return component; + } + }), + userService); when(rsp.getWriter()).thenReturn(new PrintWriter(httpOutput)); } @@ -165,7 +184,7 @@ public final class RequestHandlerTest { public void testHandleRequest_normalRequest_works() throws Exception { when(req.getMethod()).thenReturn("GET"); when(req.getRequestURI()).thenReturn("/bumblebee"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verifyZeroInteractions(rsp); verify(bumblebeeTask).run(); } @@ -174,7 +193,7 @@ public final class RequestHandlerTest { public void testHandleRequest_multipleMethodMappings_works() throws Exception { when(req.getMethod()).thenReturn("POST"); when(req.getRequestURI()).thenReturn("/bumblebee"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(bumblebeeTask).run(); } @@ -182,7 +201,7 @@ public final class RequestHandlerTest { public void testHandleRequest_prefixEnabled_subpathsWork() throws Exception { when(req.getMethod()).thenReturn("GET"); when(req.getRequestURI()).thenReturn("/bumblebee/hive"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(bumblebeeTask).run(); } @@ -190,7 +209,7 @@ public final class RequestHandlerTest { public void testHandleRequest_taskHasAutoPrintOk_printsOk() throws Exception { when(req.getMethod()).thenReturn("POST"); when(req.getRequestURI()).thenReturn("/sloth"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(slothTask).run(); verify(rsp).setContentType("text/plain; charset=utf-8"); verify(rsp).getWriter(); @@ -201,7 +220,7 @@ public final class RequestHandlerTest { public void testHandleRequest_prefixDisabled_subpathsReturn404NotFound() throws Exception { when(req.getMethod()).thenReturn("POST"); when(req.getRequestURI()).thenReturn("/sloth/nest"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(rsp).sendError(404); } @@ -209,7 +228,7 @@ public final class RequestHandlerTest { public void testHandleRequest_taskThrowsHttpException_getsHandledByHandler() throws Exception { when(req.getMethod()).thenReturn("GET"); when(req.getRequestURI()).thenReturn("/fail"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(rsp).sendError(503, "Set sail for fail"); } @@ -219,7 +238,7 @@ public final class RequestHandlerTest { throws Exception { when(req.getMethod()).thenReturn("GET"); when(req.getRequestURI()).thenReturn("/failAtConstruction"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(rsp).sendError(503, "Fail at construction"); } @@ -227,7 +246,7 @@ public final class RequestHandlerTest { public void testHandleRequest_notFound_returns404NotFound() throws Exception { when(req.getMethod()).thenReturn("GET"); when(req.getRequestURI()).thenReturn("/bogus"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(rsp).sendError(404); } @@ -235,7 +254,7 @@ public final class RequestHandlerTest { public void testHandleRequest_methodNotAllowed_returns405MethodNotAllowed() throws Exception { when(req.getMethod()).thenReturn("POST"); when(req.getRequestURI()).thenReturn("/fail"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(rsp).sendError(405); } @@ -243,7 +262,7 @@ public final class RequestHandlerTest { public void testHandleRequest_insaneMethod_returns405MethodNotAllowed() throws Exception { when(req.getMethod()).thenReturn("FIREAWAY"); when(req.getRequestURI()).thenReturn("/fail"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(rsp).sendError(405); } @@ -253,7 +272,7 @@ public final class RequestHandlerTest { public void testHandleRequest_lowercaseMethod_notRecognized() throws Exception { when(req.getMethod()).thenReturn("get"); when(req.getRequestURI()).thenReturn("/bumblebee"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(rsp).sendError(405); } @@ -268,7 +287,7 @@ public final class RequestHandlerTest { public void testXsrfProtection_noTokenProvided_returns403Forbidden() throws Exception { when(req.getMethod()).thenReturn("POST"); when(req.getRequestURI()).thenReturn("/safe-sloth"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(rsp).sendError(403, "Invalid X-CSRF-Token"); } @@ -277,7 +296,7 @@ public final class RequestHandlerTest { when(req.getMethod()).thenReturn("POST"); when(req.getHeader("X-CSRF-Token")).thenReturn(generateToken("vampire")); when(req.getRequestURI()).thenReturn("/safe-sloth"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(safeSlothTask).run(); } @@ -286,7 +305,7 @@ public final class RequestHandlerTest { when(req.getMethod()).thenReturn("POST"); when(req.getHeader("X-CSRF-Token")).thenReturn(generateToken("blood")); when(req.getRequestURI()).thenReturn("/safe-sloth"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(rsp).sendError(403, "Invalid X-CSRF-Token"); } @@ -294,7 +313,7 @@ public final class RequestHandlerTest { public void testXsrfProtection_GETMethodWithoutToken_doesntCheckToken() throws Exception { when(req.getMethod()).thenReturn("GET"); when(req.getRequestURI()).thenReturn("/safe-sloth"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(safeSlothTask).run(); } @@ -304,7 +323,7 @@ public final class RequestHandlerTest { when(userService.createLoginURL("/users-only")).thenReturn("/login"); when(req.getMethod()).thenReturn("GET"); when(req.getRequestURI()).thenReturn("/users-only"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(rsp).setStatus(302); verify(rsp).setHeader("Location", "/login"); } @@ -314,7 +333,7 @@ public final class RequestHandlerTest { when(userService.isUserLoggedIn()).thenReturn(true); when(req.getMethod()).thenReturn("GET"); when(req.getRequestURI()).thenReturn("/users-only"); - handler.handleRequest(req, rsp, component); + handler.handleRequest(req, rsp); verify(usersOnlyAction).run(); } }