ApiServlet extends HttpServlet implements UserContext { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException { try ( MDC.MDCCloseable ignored = MDC.putCloseable("clientIp", req.getRemoteAddr()); MDC.MDCCloseable ignored2 = MDC.putCloseable("requestPath", req.getContextPath() + req.getServletPath() + req.getPathInfo()) ) { controllerException.verifyNoExceptions(); invokeAction(new ServletHttpExchange(req, resp)); } } ApiServlet();  ApiServlet(Object controller); ApiControllerContext getContext(); @Override boolean isUserLoggedIn(ApiHttpExchange exchange); @Override boolean isUserInRole(ApiHttpExchange exchange, String role); void registerController(Object controller); void registerControllers(Object... controllers); void registerControllerList(List<Object> controllers); @Override void init(ServletConfig config); void registerMBeans(); void registerMBeans(MBeanServer mBeanServer); @Override TimerRegistry getTimerRegistry(); void setTimerRegistry(TimerRegistry timerRegistry);  }
@Test public void shouldCallMethodWithArgumentsAndConvertReturn() throws IOException { String name = UUID.randomUUID().toString(); FakeServletRequest request = new FakeServletRequest("GET", contextRoot, "/api", null); request.setParameter("name", name); servlet.service(request, response); assertThat(response.getStatus()).isEqualTo(200); assertThat(JsonObject.parse(new String(response.getBody())).requiredString("name")).isEqualTo(name); assertThat(response.getContentType()).isEqualTo("application/json"); }
@Test public void shouldOutputErrorToResponse() throws IOException { when(requestMock.getMethod()).thenReturn("GET"); when(requestMock.getPathInfo()).thenReturn("/error"); servlet.service(requestMock, responseMock); verify(responseMock).sendError(401, "You are not authorized"); }
@Test public void shouldGive404OnUnknownAction() throws IOException { FakeServletRequest request = new FakeServletRequest("GET", contextRoot, "/api", "/missing"); expectedLogEvents.expect(ApiControllerActionRouter.class, Level.INFO, "No route for GET " + contextRoot.getPath() + "/api[/missing]"); servlet.service(request, response); assertThat(response.getStatus()).isEqualTo(404); }
@Test public void shouldDecodePathParams() throws IOException { UUID userId = UUID.randomUUID(); when(requestMock.getMethod()).thenReturn("GET"); when(requestMock.getPathInfo()).thenReturn("/user/" + userId + "/message/abc"); servlet.service(requestMock, responseMock); verify(responseMock).sendRedirect("https: }
@Test public void shouldSendRedirect() throws IOException { when(requestMock.getScheme()).thenReturn("https"); when(requestMock.getServerName()).thenReturn("messages.example.com"); when(requestMock.getServerPort()).thenReturn(443); when(requestMock.getContextPath()).thenReturn(""); when(requestMock.getServletPath()).thenReturn(""); when(requestMock.getMethod()).thenReturn("GET"); when(requestMock.getPathInfo()).thenReturn("/redirect"); servlet.service(requestMock, responseMock); verify(responseMock).sendRedirect("/login"); }
@Test public void shouldPostJson() throws IOException { when(requestMock.getMethod()).thenReturn("POST"); when(requestMock.getPathInfo()).thenReturn("/postMethod"); JsonObject requestObject = new JsonObject() .put("foo", "bar") .put("list", Arrays.asList("a", "b", "c")); when(requestMock.getReader()) .thenReturn(new BufferedReader(new StringReader(requestObject.toIndentedJson(" ")))); servlet.service(requestMock, responseMock); assertThat(postedBody).isEqualTo(requestObject); }
@Test public void shouldGive400OnMalformedJson() throws IOException { FakeServletRequest request = new FakeServletRequest("POST", contextRoot, "/api", "/postMethod"); request.setRequestBody("This is not JSON!"); expectedLogEvents.expect(ApiControllerAction.class, Level.WARN, "While processing ServletHttpExchange[POST " + contextRoot + "/api/postMethod] arguments for ApiControllerMethodAction{POST /postMethod => ExampleController.postAction(JsonObject)}"); servlet.service(request, response); assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatusMessage()).contains("org.jsonbuddy.parse.JsonParseException: Unexpected character 'T'"); }
@Test public void shouldCallWithOptionalParameter() throws IOException { FakeServletRequest request = new FakeServletRequest("GET", contextRoot, "/api", "/hello"); assertThat(admin).isNull(); servlet.service(request, response); assertThat(admin).isEmpty(); request.setParameter("admin", "true"); servlet.service(request, response); assertThat(admin).hasValue(true); }
@Test public void shouldCallWithRequiredInt() throws IOException { when(requestMock.getMethod()).thenReturn("GET"); when(requestMock.getPathInfo()).thenReturn("/goodbye"); when(requestMock.getParameter("amount")).thenReturn("123"); servlet.service(requestMock, responseMock); assertThat(amount).isEqualTo(123); }
@Test public void shouldGive400OnParameterConversion() throws IOException { FakeServletRequest request = new FakeServletRequest("GET", contextRoot, "/api", "/goodbye"); request.setParameter("amount", "one"); servlet.service(request, response); assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatusMessage()).isEqualTo("Could not convert amount=one to int"); }
@Test public void shouldGive400OnInvalidUuidConversion() throws IOException { FakeServletRequest request = new FakeServletRequest("POST", contextRoot, "/api", "/withUuid"); request.setParameter("uuid", "Not an uuid"); expectedLogEvents.expect(ApiControllerAction.class, Level.DEBUG, "While processing ServletHttpExchange[POST " + contextRoot + "/api/withUuid?uuid=Not+an+uuid]" + " arguments to ApiControllerMethodAction{POST /withUuid => ControllerWithTypedParameters.methodWithUuid(UUID)}: " + new HttpRequestException("Could not convert uuid=Not an uuid to java.util.UUID")); servlet.service(request, response); assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatusMessage()).contains("Could not convert uuid=Not an uuid to java.util.UUID"); }
@Test public void shouldGive400OnInvalidLongConversion() throws IOException { FakeServletRequest request = new FakeServletRequest("POST", contextRoot, "/api", "/withLong"); request.setParameter("longValue", "one hundred"); expectedLogEvents.expect(ApiControllerAction.class, Level.DEBUG, "While processing ServletHttpExchange[POST " + contextRoot + "/api/withLong?longValue=one+hundred] arguments to " + "ApiControllerMethodAction{POST /withLong => ControllerWithTypedParameters.methodWithLong(long)}: " + new HttpRequestException("Could not convert longValue=one hundred to long")); servlet.service(request, response); assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatusMessage()).contains("Could not convert longValue=one hundred to long"); }
@Test public void shouldGive400OnInvalidEnumConversion() throws IOException { FakeServletRequest request = new FakeServletRequest("POST", contextRoot, "/api", "/withEnum"); request.setParameter("enumValue", "unknown"); expectedLogEvents.expect(ApiControllerAction.class, Level.DEBUG, "While processing ServletHttpExchange[POST " + contextRoot + "/api/withEnum?enumValue=unknown] arguments to " + "ApiControllerMethodAction{POST /withEnum => ControllerWithTypedParameters.methodWithEnum(ElementType)}: " + new HttpRequestException("Could not convert enumValue=unknown to java.lang.annotation.ElementType")); servlet.service(request, response); assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatusMessage()).contains("Could not convert enumValue=unknown to java.lang.annotation.ElementType"); }
@Test public void shouldRequireNonOptionalParameter() throws IOException { FakeServletRequest request = new FakeServletRequest("GET", contextRoot, "/api", "/goodbye"); expectedLogEvents.expect(ApiControllerAction.class, Level.DEBUG, "While processing ServletHttpExchange[GET " + contextRoot + "/api/goodbye] arguments to " + "ApiControllerMethodAction{GET /goodbye => ControllerWithTypedParameters.methodWithRequiredInt(int)}: " + new HttpRequestException("Missing required parameter amount")); servlet.service(request, response); assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatusMessage()).isEqualTo("Missing required parameter amount"); }
@Test public void shouldReportParameterConversionFailure() throws IOException { FakeServletRequest request = new FakeServletRequest("GET", contextRoot, "/api", "/goodbye"); request.setParameter("amount", "one"); expectedLogEvents.expectMatch(expect -> expect .level(Level.DEBUG).logger(ApiControllerAction.class) .pattern("While processing {} arguments to {}: {}") ); servlet.service(request, response); assertThat(response.getStatus()).isEqualTo(400); assertThat(response.getStatusMessage()).isEqualTo("Could not convert amount=one to int"); }
@Test public void shouldSetSessionParameters() throws IOException { when(requestMock.getMethod()).thenReturn("POST"); when(requestMock.getPathInfo()).thenReturn("/setLoggedInUser"); HttpSession mockSession = Mockito.mock(HttpSession.class); when(requestMock.getSession(Mockito.anyBoolean())).thenReturn(mockSession); servlet.service(requestMock, responseMock); verify(mockSession).setAttribute("username", "Alice Bobson"); }
@Test public void shouldRejectUnauthenticedUsersFromRestrictedOperation() throws IOException { when(requestMock.getMethod()).thenReturn("GET"); when(requestMock.getPathInfo()).thenReturn("/restricted"); servlet.service(requestMock, responseMock); verify(responseMock).setStatus(401); verify(responseMock).setContentType("application/json"); verify(responseMock).getCharacterEncoding(); verify(responseMock).setCharacterEncoding(null); verify(responseMock).getWriter(); assertThat(JsonObject.parse(responseBody.toString()).requiredString("message")) .isEqualTo("Login required"); }