//Dstl (c) Crown Copyright 2017
package uk.gov.dstl.baleen.testing.servlets;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.mockito.ArgumentCaptor;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* A helper class to allow simpler testing of servlets.
*
* Users of this class should:
*
* 1. Determine the size of the response (and then use this value in the
* responseBufferSize constructor).
*
* 2. Add parameters
*
* 3. Call the servlet with the doGet method. This will be routined to the
* corresponding do method on the servlet.
*
* 4. Use the getResponse* methods to examine the output.
*
* Note that since this does not run in an HTTP server the getResponse* values
* will be whatever was set in the servlet. If the servlet does not set a status
* code then no status code will be available here.
*
* This class has been implemented to the fidelity required for the current
* tests, and will need to be enhanced over time.
*
*
*
*/
public class ServletCaller {
private final HttpServletRequest request;
private final HttpServletResponse response;
private final Map<String, String[]> paramMap = new HashMap<>();
private final Set<String> paramNames = new HashSet<>();
private final BufferServletOutputStream outputStream;
private final ObjectMapper mapper;
private PrintWriter writer;
/**
* New isntance with default response size.
*
*/
public ServletCaller() {
this(BufferServletOutputStream.BUFFER_SIZE);
}
/**
* New instance with a specific buffer size.
*
* @param responseBufferSize
* the maximum size of response to hold (in bytes).
*/
public ServletCaller(int responseBufferSize) {
mapper = new ObjectMapper();
request = mock(HttpServletRequest.class);
response = mock(HttpServletResponse.class);
when(request.getParameterMap()).thenReturn(paramMap);
when(request.getParameterNames()).thenReturn(Collections.enumeration(paramNames));
outputStream = new BufferServletOutputStream(responseBufferSize);
writer = new PrintWriter(outputStream);
try {
doReturn(writer).when(response).getWriter();
doReturn(outputStream).when(response).getOutputStream();
} catch (IOException e) {
// Never happens
}
}
/**
* Set the request uri.
*
* @param requestUri
* (as to be returend from request.getRequestURI())
*/
public void setRequestUri(String requestUri) {
doReturn(requestUri).when(request).getRequestURI();
}
/**
* Set the servlet path.
*
* @param servletPath
* (as to be returend from request.getServletPath())
*/
public void setServletPath(String servletPath) {
doReturn(servletPath).when(request).getServletPath();
}
/**
* Set the context path.
*
* @param contextPath
* (as to be returend from request.getContextPath())
*/
public void setContextPath(String contextPath) {
doReturn(contextPath).when(request).getContextPath();
}
/**
* Add a new parameter to the request.
*
* @param key
* the parameter name
* @param values
* the values of the parameter
*/
public void addParameter(String key, String... values) {
if (values != null && values.length > 0) {
when(request.getParameter(key)).thenReturn(values[0]);
}
when(request.getParameterValues(key)).thenReturn(values);
paramMap.put(key, values);
paramNames.add(key);
}
/**
* Get the representation of the request object.
*
* This is not a complete implementation.
*
* @return
*/
public HttpServletRequest getRequest() {
return request;
}
/**
* Get the representation of the response.
*
* This is not a complete implementation.
*
*
* @return
*/
public HttpServletResponse getResponse() {
return response;
}
/**
* Simulate a GET request on this servlet.
*
* @param servlet
* the servlet instance to call
* @return the response representation (will be non-null)
* @throws ServletException
* as per HttpServlet.service
* @throws IOException
* as per HttpServlet.service
*/
public HttpServletResponse doGet(HttpServlet servlet) throws Exception {
return doMethod("GET", servlet);
}
/**
* Simulate a DELETE request on this servlet.
*
* @param servlet
* the servlet instance to call
* @return the response representation (will be non-null)
* @throws ServletException
* as per HttpServlet.service
* @throws IOException
* as per HttpServlet.service
*/
public HttpServletResponse doDelete(HttpServlet servlet) throws Exception {
return doMethod("DELETE", servlet);
}
/**
* Simulate a POST request on this servlet.
*
* @param servlet
* the servlet instance to call
* @return the response representation (will be non-null)
* @throws ServletException
* as per HttpServlet.service
* @throws IOException
* as per HttpServlet.service
*/
public HttpServletResponse doPost(HttpServlet servlet) throws Exception {
return doMethod("POST", servlet);
}
/**
* Simulate a PUT request on this servlet.
*
* @param servlet
* the servlet instance to call
* @return the response representation (will be non-null)
* @throws ServletException
* as per HttpServlet.service
* @throws IOException
* as per HttpServlet.service
*/
public HttpServletResponse doPut(HttpServlet servlet) throws Exception {
return doMethod("PUT", servlet);
}
private HttpServletResponse doMethod(String method, HttpServlet servlet) throws Exception {
when(request.getMethod()).thenReturn(method);
servlet.service(request, response);
writer.flush();
return response;
}
/**
* The written body of the response.
*
* Should only be called after calling doGet (or similar).
*
* @return the body as a UTF-8 string
*/
public String getResponseBody() {
return outputStream.getAsString();
}
/**
* Get the response as a object by parsing JSON.
*
* @param clazz
* @return object an object instance or null
*/
public <T> T getJSONResponse(Class<T> clazz) {
try {
return mapper.readValue(getResponseBody(), clazz);
} catch (Exception e) {
return null;
}
}
/**
* The content type of the response.
*
* @return the content type (if set by the servlet call, or null if not)
*/
public String getResponseType() {
ArgumentCaptor<String> t = ArgumentCaptor.forClass(String.class);
verify(response).setContentType(t.capture());
return t.getValue();
}
/**
* The status code of the response
*
* @return the status code (if set by the servlet call, or null if not)
*/
public Integer getResponseStatus() {
ArgumentCaptor<Integer> t = ArgumentCaptor.forClass(Integer.class);
verify(response).setStatus(t.capture());
return t.getValue();
}
/**
* Gets the status code as set by sendError
*
* @return the status code.
*/
public Integer getSentError() {
try {
ArgumentCaptor<Integer> c = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<String> m = ArgumentCaptor.forClass(String.class);
verify(response).sendError(c.capture(), m.capture());
return c.getValue();
} catch (IOException e) {
return null;
}
}
}