package org.stagemonitor.web.monitor.filter;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.mock.web.MockFilterConfig;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.stagemonitor.configuration.ConfigurationRegistry;
import org.stagemonitor.core.CorePlugin;
import org.stagemonitor.tracing.MonitoredRequest;
import org.stagemonitor.tracing.RequestMonitor;
import org.stagemonitor.tracing.SpanContextInformation;
import org.stagemonitor.tracing.TracingPlugin;
import org.stagemonitor.web.WebPlugin;
import org.stagemonitor.web.monitor.rum.BoomerangJsHtmlInjector;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
public class HttpRequestMonitorFilterTest {
private ConfigurationRegistry configuration = mock(ConfigurationRegistry.class);
private WebPlugin webPlugin = mock(WebPlugin.class);
private CorePlugin corePlugin = mock(CorePlugin.class);
private TracingPlugin tracingPlugin = mock(TracingPlugin.class);
private SpanContextInformation spanContext = mock(SpanContextInformation.class);
private HttpRequestMonitorFilter httpRequestMonitorFilter;
private String testHtml = "<html><body></body></html>";
@Before
public void before() throws Exception {
final RequestMonitor requestMonitor = mock(RequestMonitor.class);
when(requestMonitor.monitor(any(MonitoredRequest.class))).then(new Answer<SpanContextInformation>() {
@Override
public SpanContextInformation answer(InvocationOnMock invocation) throws Throwable {
MonitoredRequest request = (MonitoredRequest) invocation.getArguments()[0];
request.execute();
when(spanContext.getOperationName()).thenReturn("testName");
return spanContext;
}
});
when(configuration.getConfig(WebPlugin.class)).thenReturn(webPlugin);
when(configuration.getConfig(TracingPlugin.class)).thenReturn(tracingPlugin);
when(configuration.getConfig(CorePlugin.class)).thenReturn(corePlugin);
when(webPlugin.isWidgetEnabled()).thenReturn(true);
when(webPlugin.isWidgetAndStagemonitorEndpointsAllowed(any(HttpServletRequest.class), any(ConfigurationRegistry.class))).thenReturn(true);
when(corePlugin.isStagemonitorActive()).thenReturn(true);
when(tracingPlugin.getProfilerRateLimitPerMinute()).thenReturn(1000000d);
when(tracingPlugin.getRequestMonitor()).thenReturn(requestMonitor);
when(corePlugin.getApplicationName()).thenReturn("testApplication");
when(corePlugin.getInstanceName()).thenReturn("testInstance");
initFilter();
}
private void initFilter() throws Exception {
final ServletContext servlet3Context = mock(ServletContext.class);
when(servlet3Context.getMajorVersion()).thenReturn(3);
when(servlet3Context.getContextPath()).thenReturn("");
when(servlet3Context.addServlet(anyString(), any(Servlet.class))).thenReturn(mock(ServletRegistration.Dynamic.class));
final FilterConfig filterConfig = spy(new MockFilterConfig());
when(filterConfig.getServletContext()).thenReturn(servlet3Context);
httpRequestMonitorFilter = new HttpRequestMonitorFilter(configuration);
httpRequestMonitorFilter.initInternal(filterConfig);
}
@Test
public void testWidgetInjector() throws IOException, ServletException {
final MockHttpServletResponse servletResponse = new MockHttpServletResponse();
httpRequestMonitorFilter.doFilter(requestWithAccept("text/html"), servletResponse, writeInResponseWhenCallingDoFilter(testHtml));
assertTrue(servletResponse.getContentAsString().startsWith("<html><body>"));
assertTrue(servletResponse.getContentAsString().endsWith("</body></html>"));
assertFalse(servletResponse.getContentAsString().contains("beacon_url"));
assertTrue(servletResponse.getContentAsString().contains("window.StagemonitorLoaded"));
}
@Test
public void testBinaryData() throws IOException, ServletException {
final MockHttpServletResponse servletResponse = new MockHttpServletResponse();
httpRequestMonitorFilter.doFilter(requestWithAccept("text/html"), servletResponse,
writeBinaryDataInResponseWhenCallingDoFilter(new byte[] {1}));
assertEquals(1, servletResponse.getContentAsByteArray().length);
assertEquals(1, servletResponse.getContentAsByteArray()[0]);
}
private MockHttpServletRequest requestWithAccept(String accept) {
final MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest();
mockHttpServletRequest.addHeader("accept", accept);
return mockHttpServletRequest;
}
@Test
public void testWidgetShouldNotBeInjectedIfInjectionDisabled() throws IOException, ServletException {
when(webPlugin.isRealUserMonitoringEnabled()).thenReturn(false);
when(webPlugin.isWidgetAndStagemonitorEndpointsAllowed(any(HttpServletRequest.class), any(ConfigurationRegistry.class))).thenReturn(false);
when(webPlugin.isWidgetEnabled()).thenReturn(false);
final MockHttpServletResponse servletResponse = new MockHttpServletResponse();
httpRequestMonitorFilter.doFilter(requestWithAccept("text/html"), servletResponse, writeInResponseWhenCallingDoFilter(testHtml));
final String expected = "<html><body></body></html>";
Assert.assertEquals(expected, servletResponse.getContentAsString());
}
@Test
public void testWidgetShouldNotBeInjectedIfHtmlIsNotAcceptable() throws IOException, ServletException {
final MockHttpServletResponse servletResponse = new MockHttpServletResponse();
httpRequestMonitorFilter.doFilter(requestWithAccept("application/json"), servletResponse, writeInResponseWhenCallingDoFilter(testHtml));
final String expected = "<html><body></body></html>";
Assert.assertEquals(expected, servletResponse.getContentAsString());
}
@Test
public void testWidgetInjectorWithMultipleBodyTags() throws IOException, ServletException {
final MockHttpServletResponse servletResponse = new MockHttpServletResponse();
final String html = "<html><body></body><body></body><body></body><body>asdf</body></html>";
httpRequestMonitorFilter.doFilter(requestWithAccept("text/html"), servletResponse, writeInResponseWhenCallingDoFilter(html));
assertTrue(servletResponse.getContentAsString().startsWith("<html><body></body><body></body><body></body><body>asdf"));
assertTrue(servletResponse.getContentAsString().endsWith("</body></html>"));
assertFalse(servletResponse.getContentAsString().contains("beacon_url"));
assertTrue(servletResponse.getContentAsString().contains("window.StagemonitorLoaded"));
}
private FilterChain writeInResponseWhenCallingDoFilter(final String html) throws IOException, ServletException {
final FilterChain filterChain = mock(FilterChain.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
HttpServletResponse response = (HttpServletResponse) invocation.getArguments()[1];
if (Math.random() > 0.5) {
System.out.println("using writer");
response.getWriter().write(html);
} else {
System.out.println("using output stream");
response.getOutputStream().print(html);
}
response.flushBuffer();
response.setContentType("text/html");
return null;
}
}).when(filterChain).doFilter(any(ServletRequest.class), any(ServletResponse.class));
return filterChain;
}
private FilterChain writeBinaryDataInResponseWhenCallingDoFilter(final byte[] bytes) throws IOException, ServletException {
final FilterChain filterChain = mock(FilterChain.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
HttpServletResponse response = (HttpServletResponse) invocation.getArguments()[1];
response.getOutputStream().write(bytes);
response.flushBuffer();
response.setContentType("text/html");
return null;
}
}).when(filterChain).doFilter(any(ServletRequest.class), any(ServletResponse.class));
return filterChain;
}
@Test
public void testRUM() throws Exception {
when(webPlugin.isRealUserMonitoringEnabled()).thenReturn(true);
when(webPlugin.isWidgetEnabled()).thenReturn(false);
when(webPlugin.isWidgetAndStagemonitorEndpointsAllowed(any(HttpServletRequest.class), any(ConfigurationRegistry.class))).thenReturn(false);
initFilter();
final MockHttpServletResponse servletResponse = new MockHttpServletResponse();
httpRequestMonitorFilter.doFilter(requestWithAccept("text/html"), servletResponse, writeInResponseWhenCallingDoFilter(testHtml));
Assert.assertEquals("<html><body><script src=\"/stagemonitor/public/static/rum/" + BoomerangJsHtmlInjector.BOOMERANG_FILENAME + "\"></script>\n" +
"<script>\n" +
" BOOMR.init({\n" +
" beacon_url: '/stagemonitor/public/rum',\n" +
" log: null\n" +
" });\n" +
" BOOMR.addVar(\"requestName\", \"testName\");\n" +
" BOOMR.addVar(\"serverTime\", 0);\n" +
"</script></body></html>", servletResponse.getContentAsString());
}
}