package org.stagemonitor.web.monitor.widget; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.stagemonitor.configuration.ConfigurationOption; import org.stagemonitor.configuration.ConfigurationRegistry; import org.stagemonitor.core.CorePlugin; import org.stagemonitor.core.metrics.metrics2.Metric2Registry; import org.stagemonitor.core.util.JsonUtils; import org.stagemonitor.tracing.RequestMonitor; import org.stagemonitor.tracing.SpanContextInformation; import org.stagemonitor.tracing.TracingPlugin; import org.stagemonitor.tracing.reporter.ReportingSpanEventListener; import org.stagemonitor.tracing.sampling.SamplePriorityDeterminingSpanEventListener; import org.stagemonitor.tracing.tracing.B3Propagator; import org.stagemonitor.tracing.utils.SpanUtils; import org.stagemonitor.tracing.wrapper.SpanEventListenerFactory; import org.stagemonitor.util.StringUtils; import org.stagemonitor.web.WebPlugin; import org.stagemonitor.web.monitor.MonitoredHttpRequest; import org.stagemonitor.web.monitor.filter.StagemonitorSecurityFilter; import org.stagemonitor.web.monitor.filter.StatusExposingByteCountingServletResponse; import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.Collections; import java.util.ServiceLoader; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import io.opentracing.Span; import io.opentracing.Tracer; import io.opentracing.mock.MockTracer; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class SpanServletTest { private WidgetAjaxSpanReporter reporter; private SpanServlet spanServlet; private String connectionId; private WebPlugin webPlugin; private SpanContextInformation spanContext; private ConfigurationRegistry configuration; @Before public void setUp() throws Exception { configuration = mock(ConfigurationRegistry.class); TracingPlugin tracingPlugin = mock(TracingPlugin.class); when(tracingPlugin.getRequestMonitor()).thenReturn(mock(RequestMonitor.class)); when(tracingPlugin.getProfilerRateLimitPerMinuteOption()).thenReturn(mock(ConfigurationOption.class)); when(configuration.getConfig(TracingPlugin.class)).thenReturn(tracingPlugin); webPlugin = mock(WebPlugin.class); when(webPlugin.isWidgetAndStagemonitorEndpointsAllowed(any(HttpServletRequest.class), any(ConfigurationRegistry.class))).thenReturn(Boolean.TRUE); when(configuration.getConfig(WebPlugin.class)).thenReturn(webPlugin); final CorePlugin corePlugin = mock(CorePlugin.class); when(corePlugin.getThreadPoolQueueCapacityLimit()).thenReturn(1000); when(configuration.getConfig(CorePlugin.class)).thenReturn(corePlugin); reporter = new WidgetAjaxSpanReporter(); spanServlet = new SpanServlet(configuration, reporter, 1500); spanServlet.init(); connectionId = UUID.randomUUID().toString(); final SamplePriorityDeterminingSpanEventListener samplePriorityDeterminingSpanInterceptor = mock(SamplePriorityDeterminingSpanEventListener.class); when(samplePriorityDeterminingSpanInterceptor.onSetTag(anyString(), anyString())).then(invocation -> invocation.getArgument(1)); when(samplePriorityDeterminingSpanInterceptor.onSetTag(anyString(), anyBoolean())).then(invocation -> invocation.getArgument(1)); when(samplePriorityDeterminingSpanInterceptor.onSetTag(anyString(), any(Number.class))).then(invocation -> invocation.getArgument(1)); final ReportingSpanEventListener reportingSpanEventListener = new ReportingSpanEventListener(configuration); reportingSpanEventListener.addReporter(reporter); Tracer tracer = TracingPlugin.createSpanWrappingTracer(new MockTracer(new B3Propagator()), configuration, new Metric2Registry(), ServiceLoader.load(SpanEventListenerFactory.class), samplePriorityDeterminingSpanInterceptor, reportingSpanEventListener); when(tracingPlugin.getTracer()).thenReturn(tracer); } private void reportSpan() { final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test"); request.addHeader(WidgetAjaxSpanReporter.CONNECTION_ID, connectionId); final MonitoredHttpRequest monitoredHttpRequest = new MonitoredHttpRequest(request, mock(StatusExposingByteCountingServletResponse.class), new MockFilterChain(), configuration); Span span = monitoredHttpRequest.createSpan(); spanContext = SpanContextInformation.forSpan(span); span.setOperationName("test"); span.finish(); } @Test public void testSpanBeforeRequest() throws Exception { reportSpan(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/stagemonitor/spans"); request.addParameter("connectionId", connectionId); MockHttpServletResponse response = new MockHttpServletResponse(); spanServlet.service(request, response); Assert.assertEquals(spanAsJsonArray(), response.getContentAsString()); Assert.assertEquals("application/json;charset=UTF-8", response.getHeader("content-type")); } @Test public void testTwoSpanBeforeRequest() throws Exception { reportSpan(); final String span1 = spanAsJson(); reportSpan(); final String span2 = spanAsJson(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/stagemonitor/spans"); request.addParameter("connectionId", connectionId); MockHttpServletResponse response = new MockHttpServletResponse(); spanServlet.service(request, response); Assert.assertEquals(Arrays.asList(span1, span2).toString(), response.getContentAsString()); Assert.assertEquals("application/json;charset=UTF-8", response.getHeader("content-type")); } private String spanAsJsonArray() { return Collections.singletonList(spanAsJson()).toString(); } private String spanAsJson() { return JsonUtils.toJson(spanContext.getReadbackSpan(), SpanUtils.CALL_TREE_ASCII); } private void performNonBlockingRequest(final HttpServletRequest request, final MockHttpServletResponse response) throws Exception { final Object lock = new Object(); synchronized (lock) { new Thread(new Runnable() { @Override public void run() { try { synchronized (lock) { lock.notifyAll(); } spanServlet.service(request, response); } catch (Exception e) { e.printStackTrace(); } } }).start(); lock.wait(); } // Thread.sleep(100); } private void waitForResponse(MockHttpServletResponse response) throws UnsupportedEncodingException, InterruptedException { final int maxWait = 2_000; int wait = 0; while (StringUtils.isEmpty(response.getContentAsString()) && wait <= maxWait) { wait += 10; Thread.sleep(10); } } @Test public void testSpanAfterRequest() throws Exception { final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/stagemonitor/spans"); request.addParameter("connectionId", connectionId); request.setAsyncSupported(false); final MockHttpServletResponse response = new MockHttpServletResponse(); performNonBlockingRequest(request, response); reportSpan(); waitForResponse(response); Assert.assertEquals(spanAsJsonArray(), response.getContentAsString()); Assert.assertEquals("application/json;charset=UTF-8", response.getHeader("content-type")); } @Test public void testSpanAfterRequestDifferentConnection() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/stagemonitor/spans"); request.addParameter("connectionId", UUID.randomUUID().toString()); request.setAsyncSupported(true); MockHttpServletResponse response = new MockHttpServletResponse(); performNonBlockingRequest(request, response); reportSpan(); waitForResponse(response); Assert.assertEquals("[]", response.getContentAsString()); } @Test public void testMissingConnectionId() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/stagemonitor/spans"); MockHttpServletResponse response = new MockHttpServletResponse(); spanServlet.service(request, response); Assert.assertEquals(400, response.getStatus()); } @Test public void testInvalidConnectionId() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/stagemonitor/spans"); request.addParameter("connectionId", ""); MockHttpServletResponse response = new MockHttpServletResponse(); spanServlet.service(request, response); Assert.assertEquals(400, response.getStatus()); } @Test public void testWidgetDeactivated() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/stagemonitor/spans"); request.addParameter("connectionId", ""); MockHttpServletResponse response = new MockHttpServletResponse(); Mockito.when(webPlugin.isWidgetAndStagemonitorEndpointsAllowed(eq(request), any(ConfigurationRegistry.class))).thenReturn(Boolean.FALSE); ConfigurationRegistry configuration = mock(ConfigurationRegistry.class); when(configuration.getConfig(WebPlugin.class)).thenReturn(webPlugin); new MockFilterChain(spanServlet, new StagemonitorSecurityFilter(configuration)).doFilter(request, response); Assert.assertEquals(404, response.getStatus()); } }