From 7757e895f5f5fcc2fa3335889251a12d845e1256 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Thu, 5 Jun 2014 11:13:41 +0200 Subject: [PATCH] Add test to the default error handler on the pipeline error proper handling (#26) Signed-off-by: Clement Escoffier --- .../wisdom/error/DefaultPageErrorHandler.java | 45 ++++++++-- .../error/DefaultPageErrorHandlerTest.java | 85 +++++++++++++++++-- .../src/test/resources/pipeline/error.json | 5 ++ .../src/test/resources/wisdom/INFO.txt | 1 + 4 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 framework/default-error-handler/src/test/resources/pipeline/error.json create mode 100644 framework/default-error-handler/src/test/resources/wisdom/INFO.txt diff --git a/framework/default-error-handler/src/main/java/org/wisdom/error/DefaultPageErrorHandler.java b/framework/default-error-handler/src/main/java/org/wisdom/error/DefaultPageErrorHandler.java index 2e8781f39..36e25e38f 100644 --- a/framework/default-error-handler/src/main/java/org/wisdom/error/DefaultPageErrorHandler.java +++ b/framework/default-error-handler/src/main/java/org/wisdom/error/DefaultPageErrorHandler.java @@ -80,7 +80,7 @@ public class DefaultPageErrorHandler extends DefaultController implements Filter * The 500 template. */ @Requires(filter = "(name=error/pipeline)", proxy = false, optional = true, id = "pipeline") - private Template pipeline; + protected Template pipeline; /** * The router. @@ -100,11 +100,19 @@ public class DefaultPageErrorHandler extends DefaultController implements Filter private File pipelineErrorDirectory; + /** + * Methods called when this component is starting. It builds the pipeline error directory from the + * configuration's base directory. + */ @Validate public void start() { pipelineErrorDirectory = new File(configuration.getBaseDir().getParentFile(), "pipeline"); } + /** + * @return the first error file contained in the pipeline error's directory, {@literal null} if none. Notice that + * the returned file may depend on the operating system. + */ public File getFirstErrorFile() { if (!pipelineErrorDirectory.isDirectory()) { return null; @@ -127,7 +135,7 @@ public File getFirstErrorFile() { * @return the HTTP result serving the error page */ private Result renderInternalError(Context context, Route route, Throwable e) { - Throwable localException = e; + Throwable localException; // If the template is not there, just wrap the exception within a JSON Object. if (internalerror == null) { @@ -136,8 +144,10 @@ private Result renderInternalError(Context context, Route route, Throwable e) { // Manage ITE - if (localException instanceof InvocationTargetException) { - localException = ((InvocationTargetException) localException).getTargetException(); + if (e instanceof InvocationTargetException) { + localException = ((InvocationTargetException) e).getTargetException(); + } else { + localException = e; } // Retrieve the cause if any. @@ -370,16 +380,38 @@ public int priority() { return 1000; } + /** + * A structure storing the line of an error. + */ public static class InterestingLines { + /** + * The first line to display (line number). + */ public final int firstLine; + + /** + * The line where the error occurs (line number). + */ public final int errorLine; + + /** + * The set of lines. + */ public final String[] focus; - public InterestingLines(int firstLine, String[] focus, int errorLine) { + /** + * Creates the interested line instance. + * + * @param firstLine the first line + * @param focus the set of lines + * @param errorLine the error line. + */ + private InterestingLines(int firstLine, String[] focus, int errorLine) { this.firstLine = firstLine; this.errorLine = errorLine; - this.focus = focus; + // We keep a copy of the array. + this.focus = focus; //NOSONAR } } @@ -390,6 +422,7 @@ public InterestingLines(int firstLine, String[] focus, int errorLine) { * @param source the source * @param line the line responsible of the error * @param border number of lines to use as a border + * @return the interested line structure */ public InterestingLines extractInterestedLines(String source, int line, int border) { try { diff --git a/framework/default-error-handler/src/test/java/org/wisdom/error/DefaultPageErrorHandlerTest.java b/framework/default-error-handler/src/test/java/org/wisdom/error/DefaultPageErrorHandlerTest.java index 81217fe04..3ff405746 100644 --- a/framework/default-error-handler/src/test/java/org/wisdom/error/DefaultPageErrorHandlerTest.java +++ b/framework/default-error-handler/src/test/java/org/wisdom/error/DefaultPageErrorHandlerTest.java @@ -19,25 +19,31 @@ */ package org.wisdom.error; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; +import org.junit.After; import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import org.wisdom.api.Controller; import org.wisdom.api.DefaultController; import org.wisdom.api.configuration.ApplicationConfiguration; -import org.wisdom.api.http.HttpMethod; -import org.wisdom.api.http.MimeTypes; -import org.wisdom.api.http.Result; -import org.wisdom.api.http.Status; +import org.wisdom.api.content.Json; +import org.wisdom.api.http.*; import org.wisdom.api.interception.Filter; import org.wisdom.api.interception.Interceptor; import org.wisdom.api.interception.RequestContext; import org.wisdom.api.router.Route; import org.wisdom.api.router.Router; +import org.wisdom.api.templates.Template; +import java.io.File; import java.util.Collections; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -105,7 +111,6 @@ public void switchToHeadWhenGetRouteDoesNotExist() throws Exception { when(handler.configuration.isDev()).thenReturn(false); handler.router = mock(Router.class); - Route reqRoute = new Route(HttpMethod.HEAD, "/", null, null); when(handler.router.getRouteFor(HttpMethod.HEAD, "/")).thenReturn(new Route(HttpMethod.HEAD, "/", null, null)); @@ -120,6 +125,76 @@ public void switchToHeadWhenGetRouteDoesNotExist() throws Exception { assertThat(result.getRenderable().length()).isEqualTo(0); } + @Test + public void pipelineErrorWithoutError() throws Exception { + DefaultPageErrorHandler handler = new DefaultPageErrorHandler(); + handler.configuration = mock(ApplicationConfiguration.class); + when(handler.configuration.isDev()).thenReturn(true); + when(handler.configuration.getBaseDir()).thenReturn(new File("src/test/resources/wisdom/missing_on_purpose")); + handler.router = mock(Router.class); + handler.pipeline = mock(Template.class); + + Request request = mock(Request.class); + when(request.accepts(MimeTypes.HTML)).thenReturn(true); + + Context context = mock(Context.class); + when(context.request()).thenReturn(request); + + Context.CONTEXT.set(context); + MyController controller = new MyController(); + Route route = new Route(HttpMethod.GET, "/", controller, controller.getClass().getMethod("action")); + + + RequestContext rc = new RequestContext(route, Collections.emptyList(), + Collections., Object>emptyMap(), new Object[0]); + + handler.start(); + Result result = handler.call(route, rc); + assertThat(result.getStatusCode()).isEqualTo(200); + } + + @Test + public void pipelineErrorWithError() throws Exception { + DefaultPageErrorHandler handler = new DefaultPageErrorHandler(); + handler.configuration = mock(ApplicationConfiguration.class); + when(handler.configuration.isDev()).thenReturn(true); + when(handler.configuration.getBaseDir()).thenReturn(new File("src/test/resources/wisdom")); + + handler.router = mock(Router.class); + handler.pipeline = mock(Template.class); + handler.json = mock(Json.class); + when(handler.json.parse(anyString())).thenAnswer(new Answer() { + @Override + public JsonNode answer(InvocationOnMock invocation) throws Throwable { + return new ObjectMapper().readValue((String) invocation.getArguments()[0], JsonNode.class); + } + }); + + handler.start(); + + Request request = mock(Request.class); + when(request.accepts(MimeTypes.HTML)).thenReturn(true); + + Context context = mock(Context.class); + when(context.request()).thenReturn(request); + + Context.CONTEXT.set(context); + MyController controller = new MyController(); + Route route = new Route(HttpMethod.GET, "/", controller, controller.getClass().getMethod("action")); + + + RequestContext rc = new RequestContext(route, Collections.emptyList(), + Collections., Object>emptyMap(), new Object[0]); + + Result result = handler.call(route, rc); + assertThat(result.getStatusCode()).isEqualTo(500); + } + + @After + public void cleanupContext() { + Context.CONTEXT.remove(); + } + private class MyController extends DefaultController { diff --git a/framework/default-error-handler/src/test/resources/pipeline/error.json b/framework/default-error-handler/src/test/resources/pipeline/error.json new file mode 100644 index 000000000..26d0f7c19 --- /dev/null +++ b/framework/default-error-handler/src/test/resources/pipeline/error.json @@ -0,0 +1,5 @@ +{ + "message" : "This is the error", + "title" : "processing error", + "watcher" : "My Watcher" +} \ No newline at end of file diff --git a/framework/default-error-handler/src/test/resources/wisdom/INFO.txt b/framework/default-error-handler/src/test/resources/wisdom/INFO.txt new file mode 100644 index 000000000..faa6ab004 --- /dev/null +++ b/framework/default-error-handler/src/test/resources/wisdom/INFO.txt @@ -0,0 +1 @@ +This directory is just here to simulate the location of the Wisdom Runtime. \ No newline at end of file