Java tutorial
/* * Copyright (C) 2013 salesforce.com, inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.auraframework.integration.test.service; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.ConcurrentModificationException; import java.util.EmptyStackException; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.http.HttpStatus; import org.auraframework.adapter.ConfigAdapter; import org.auraframework.adapter.ExceptionAdapter; import org.auraframework.def.ActionDef; import org.auraframework.def.ApplicationDef; import org.auraframework.def.ComponentDef; import org.auraframework.def.DefDescriptor; import org.auraframework.def.Definition; import org.auraframework.def.DefinitionAccess; import org.auraframework.def.TypeDef; import org.auraframework.def.ValueDef; import org.auraframework.impl.AuraImplTestCase; import org.auraframework.impl.adapter.ServletUtilAdapterImpl; import org.auraframework.instance.AbstractActionImpl; import org.auraframework.instance.Action; import org.auraframework.instance.ActionDelegate; import org.auraframework.instance.Component; import org.auraframework.instance.InstanceStack; import org.auraframework.service.ContextService; import org.auraframework.service.DefinitionService; import org.auraframework.service.InstanceService; import org.auraframework.service.SerializationService; import org.auraframework.service.ServerService; import org.auraframework.system.AuraContext; import org.auraframework.system.AuraContext.Authentication; import org.auraframework.system.AuraContext.Format; import org.auraframework.system.AuraContext.Mode; import org.auraframework.system.Location; import org.auraframework.system.Message; import org.auraframework.system.SubDefDescriptor; import org.auraframework.throwable.AuraExecutionException; import org.auraframework.throwable.quickfix.QuickFixException; import org.auraframework.util.json.Json; import org.auraframework.util.json.JsonReader; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import javax.inject.Inject; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; public class ServerServiceImplTest extends AuraImplTestCase { @Inject private InstanceService instanceService; @Inject private ServerService serverService; public ServerServiceImplTest() { super(); setShouldSetupContext(false); } private static final Set<String> GLOBAL_IGNORE = Sets.newHashSet("context", "actions", "perf"); // Do not test for null message, it cannot legally be null. private static class EmptyActionDef implements ActionDef { private static final long serialVersionUID = 1L; StringWriter sw; String name; protected EmptyActionDef(StringWriter sw, String name) { this.sw = sw; this.name = name; } @Override public void validateDefinition() throws QuickFixException { } @Override public void appendDependencies(Set<DefDescriptor<?>> dependencies) { } @Override public void validateReferences() throws QuickFixException { } @Override public void markValid() { } @Override public boolean isValid() { return true; } @Override public Location getLocation() { return null; } @Override public DefinitionAccess getAccess() { return null; } @Override public <D extends Definition> D getSubDefinition(SubDefDescriptor<D, ?> descriptor) { return null; } @Override public void retrieveLabels() throws QuickFixException { } @Override public String getAPIVersion() { return null; } @Override public String getDescription() { return null; } @Override public String getOwnHash() { return null; } @Override public void appendSupers(Set<DefDescriptor<?>> supers) throws QuickFixException { } @Override public void serialize(Json json) throws IOException { } @Override public DefDescriptor<ActionDef> getDescriptor() { return null; } @Override public ActionType getActionType() { return null; } @Override public String getName() { return this.name; } @Override public DefDescriptor<TypeDef> getReturnType() { return null; } @Override public List<ValueDef> getParameters() { return null; } @Override public List<String> getLoggableParams() { return Lists.newArrayList(); } } private class EmptyAction extends AbstractActionImpl<EmptyActionDef> { private String returnValue = ""; private Integer count = 0; private String parameter = ""; private DefinitionService definitionService; public EmptyAction(StringWriter sw, String name, DefinitionService definitionService) { super(null, new EmptyActionDef(sw, name), null); this.definitionService = definitionService; } public EmptyAction(DefinitionService definitionService) { super(null, new EmptyActionDef(null, "simpleaction"), null); this.definitionService = definitionService; } public Integer getCount() { return this.count; } public String getName() { return this.actionDef.getName(); } @Override public DefDescriptor<ActionDef> getDescriptor() { return definitionService.getDefDescriptor("java://aura.empty/ACTION$emptyAction", ActionDef.class); } @Override public void run() throws AuraExecutionException { this.count++; if (this.actionDef.sw != null) { this.returnValue = this.actionDef.sw.toString(); } else { // do nothing } if (this.count > 1) { setParameter("#" + this.count); } } @Override public Object getReturnValue() { return this.returnValue; } private void setParameter(String parameter) { this.parameter = parameter; } @Override public List<Object> getErrors() { return null; } @Override public void serialize(Json json) throws IOException { Map<String, String> value = Maps.newHashMap(); String res = this.getName(); if (this.parameter != "") { res = res.concat("{" + this.parameter + "}"); } value.put("action", res); json.writeValue(value); } }; private static class ShareCmpAction extends ActionDelegate { private Map<String, Object> componentAttributes = null; private Component sharedCmp = null; private Object returnValue = null; private String name = "ShareCmpAction"; public ShareCmpAction(String name, Action originalAction, Component sharedCmp, Map<String, Object> componentAttributes) { super(originalAction); this.sharedCmp = sharedCmp; this.componentAttributes = componentAttributes; this.name = name; } @Override public void run() throws AuraExecutionException { try { sharedCmp.getAttributes().set(componentAttributes); } catch (QuickFixException e) { throw new AuraExecutionException(e.getMessage(), e.getLocation()); } super.run(); String whatIsInResponse = (String) super.getReturnValue(); int startPos = whatIsInResponse.lastIndexOf("shared_component"); if (startPos >= 0) { this.returnValue = whatIsInResponse.substring(startPos); } } @Override public Object getReturnValue() { return this.returnValue; } @Override public void serialize(Json json) throws IOException { Map<String, Object> value = Maps.newHashMap(); value.put("shared_component", this.sharedCmp); value.put("action", this.name); json.writeValue(value); } } /** * Test for W-2085617 This test is to verify when we have shared component between actions, they get serialized into * response correctly. * * Test Setup: EmptyAction a,b,c : when it run, it put whatever response has into their return value ShareCmpAction * d,e,f: when it run, it update the attribute of shared component, run its delegate action(a,b orc), then get the * latest shared_component(in Json format) from its delegate action's return value as its return value. * * when b runs, a has finish running, so b will have shared_component of a e will have shared_component of a in its * return value (with attrA) when c runs, b has finish running, so c will have shared_components of a & b e will * have shared_component of b in its return value (with attrB) * * @throws Exception */ @Test public void testSharedCmp() throws Exception { contextService.startContext(Mode.UTEST, Format.JSON, Authentication.AUTHENTICATED); Map<String, Object> attributes = Maps.newHashMap(); Map<String, Object> attributesA = Maps.newHashMap(); attributesA.put("attr", "attrA"); Map<String, Object> attributesB = Maps.newHashMap(); attributesB.put("attr", "attrB"); Map<String, Object> attributesC = Maps.newHashMap(); attributesC.put("attr", "attrC"); Component sharedCmp = instanceService.getInstance("ifTest:testIfWithModel", ComponentDef.class, attributes); StringWriter sw = new StringWriter(); ServerService ss = serverService; Action a = new EmptyAction(sw, "first action", definitionService); Action b = new EmptyAction(sw, "second action", definitionService); Action c = new EmptyAction(sw, "third action", definitionService); Action d = new ShareCmpAction("d", a, sharedCmp, attributesA); Action e = new ShareCmpAction("e", b, sharedCmp, attributesB); Action f = new ShareCmpAction("f", c, sharedCmp, attributesC); List<Action> actions = Lists.newArrayList(d, e, f); Message message = new Message(actions); // run the list of actions. ss.run(message, contextService.getCurrentContext(), sw, null); // sanity check, sharedCmp should have the latest attribute value. // this has nothing to do with the fix though assertEquals("attrC", sharedCmp.getAttributes().getValue("attr")); // Here are the checks for fix // returnValue of action e is going to have shared component from action d in Json format String returne = (String) e.getReturnValue(); assertTrue(returne.contains("markup://ifTest:testIfWithModel")); assertTrue(returne.contains("\"attr\":\"attrA\"")); // returnValue of action f is going to have shared component from action e in Json format String returnf = (String) f.getReturnValue(); assertTrue(returnf.contains("markup://ifTest:testIfWithModel")); assertTrue(returnf.contains("\"attr\":\"attrB\"")); } /** * Check that our EmptyAction is properly serialized. * * This does a positive and negative test, ensuring that we only serialize what we should. */ @SuppressWarnings("unchecked") private Map<String, Object> validateEmptyActionSerialization(String serialized, Set<String> ignore, List<String> actionNameList) { int actionNumber = actionNameList.size(); Set<String> extras = Sets.newHashSet(); Map<String, Object> json = (Map<String, Object>) new JsonReader().read(serialized); List<Object> actions = (List<Object>) json.get("actions"); assertTrue(actions != null); assertTrue("expected " + actionNumber + " action, but get " + actions.size(), actions.size() == actionNumber); for (int i = 0; i < actionNumber; i++) { Map<String, Object> action = (Map<String, Object>) actions.get(i); assertEquals("didn't get expecting action on i:" + i, actionNameList.get(i), action.get("action")); } for (String key : json.keySet()) { if (!GLOBAL_IGNORE.contains(key) && (ignore == null || !ignore.contains(key))) { extras.add(key); } } assertTrue("Expected no extra keys, found: " + extras + ", in: " + json, extras.isEmpty()); return json; } /** * This test is for W-2063110 Test a list of actions. New Way : in ServerService, we serialize each action and write * it into response (via a string writer) right after it finish running. in the SimpleAction above, we put whatever * we have in the string writer as returnValue of the current action so when Action2 is running, we should have * Action1 in string writer, when Action3 is running, we should have both Action1 and Action2, .... Old Way: We used * to run all actions, store the result in Message, then write them into response at once, in the old way we won't * have anything in string writer/response until Action3 is finished. */ @Test public void testMultipleActions() throws Exception { contextService.startContext(Mode.UTEST, Format.JSON, Authentication.AUTHENTICATED); StringWriter sw = new StringWriter(); ServerService ss = serverService; Action a = new EmptyAction(sw, "first action", definitionService); Action b = new EmptyAction(sw, "second action", definitionService); Action c = new EmptyAction(sw, "third action", definitionService); List<Action> actions = Lists.newArrayList(a, b, c); Message message = new Message(actions); // run the list of actions. ss.run(message, contextService.getCurrentContext(), sw, null); String returnValuea = "{\"actions\":["; String returnValueb = returnValuea + "{\"action\":\"firstaction\"}"; String returnValuec = returnValueb + ",{\"action\":\"secondaction\"}"; List<String> returnValueList = Arrays.asList(returnValuea, returnValueb, returnValuec); for (int i = 0; i < actions.size(); i++) { Action act = actions.get(i); assertEquals("get different action return on i:" + i, returnValueList.get(i), ((String) act.getReturnValue()).replaceAll("\\s+", "")); } validateEmptyActionSerialization(sw.toString(), null, Arrays.asList("first action", "second action", "third action")); } /** * This test is for W-2063110 Running the same action twice in a list since we output right after the run, we can * reuse the action. the second run will over-write the previous run's returnValue(unless we change the run()), but * response keep the info from previous run, so we are good */ @Test public void testSameActionTwice() throws Exception { contextService.startContext(Mode.UTEST, Format.JSON, Authentication.AUTHENTICATED); StringWriter sw = new StringWriter(); ServerService ss = serverService; Action a = new EmptyAction(sw, "first action", definitionService); Action b = new EmptyAction(sw, "second action", definitionService); List<Action> actions = Lists.newArrayList(a, b, a, b); Message message = new Message(actions); ss.run(message, contextService.getCurrentContext(), sw, null); assertTrue(((EmptyAction) a).getCount() == 2); assertTrue(((EmptyAction) b).getCount() == 2); // in the old way since we output action info into response after all actions finish running, the previous run's // info will get overwrited // but this is not the case now // we need to verify when same action run twice, and something about the action changed between the two runs // --like the parameter, // the response has the action info for both times. validateEmptyActionSerialization(sw.toString(), null, Arrays.asList("first action", "second action", "first action{#2}", "second action{#2}")); } /** * Test a simple action that serializes a specific value. * * We carefully test only the parts that we care about for ServerService. */ @Test public void testSimpleAction() throws Exception { contextService.startContext(Mode.UTEST, Format.JSON, Authentication.AUTHENTICATED); ServerService ss = serverService; Action a = new EmptyAction(definitionService); List<Action> actions = Lists.newArrayList(a); Message message = new Message(actions); StringWriter sw = new StringWriter(); ss.run(message, contextService.getCurrentContext(), sw, null); validateEmptyActionSerialization(sw.toString(), null, Arrays.asList("simpleaction")); } /** * Test a simple action that serializes a specific value. * * We carefully test only the parts that we care about for ServerService. */ @Test public void testSimpleActionWithExtras() throws Exception { contextService.startContext(Mode.UTEST, Format.JSON, Authentication.AUTHENTICATED); ServerService ss = serverService; Action a = new EmptyAction(definitionService); List<Action> actions = Lists.newArrayList(a); Map<String, String> extras = Maps.newHashMap(); Message message = new Message(actions); StringWriter sw = new StringWriter(); extras.put("this", "that"); ss.run(message, contextService.getCurrentContext(), sw, extras); Map<String, Object> json = validateEmptyActionSerialization(sw.toString(), Sets.newHashSet("this"), Arrays.asList("simpleaction")); assertEquals("Expected extras to be in " + json, "that", json.get("this")); } /** * Sanity check to make sure that app.css does not have duplicate copy of component CSS. Component CSS was being * added twice, once because they were part of preload namespace and a second time because of component dependency. * This test mocks such duplication. W-1588568 */ @Test public void testWriteCssWithoutDupes() throws Exception { ServerService ss = serverService; DefDescriptor<ApplicationDef> appDesc = definitionService .getDefDescriptor("preloadTest:test_SimpleApplication", ApplicationDef.class); AuraContext context = contextService.startContext(Mode.DEV, AuraContext.Format.CSS, AuraContext.Authentication.AUTHENTICATED, appDesc); final String uid = context.getDefRegistry().getUid(null, appDesc); context.addLoaded(appDesc, uid); Set<DefDescriptor<?>> dependencies = context.getDefRegistry().getDependencies(uid); StringWriter output = new StringWriter(); ss.writeAppCss(dependencies, output); // A snippet of component css String cssPiece = "AuraResourceServletTest-testWriteCssWithoutDupes"; Pattern pattern = Pattern.compile(cssPiece); Matcher matcher = pattern.matcher(output.toString()); int count = 0; while (matcher.find() && count < 3) { count++; } assertEquals("Component CSS repeated", 1, count); } /** * Verify that the css writer writes in the order given. */ @Test public void testCSSOrder() throws Exception { ServerService ss = serverService; DefDescriptor<ApplicationDef> appDesc = definitionService .getDefDescriptor("auratest:test_SimpleServerRenderedPage", ApplicationDef.class); DefDescriptor<ComponentDef> grandparent = definitionService .getDefDescriptor("setAttributesTest:grandparent", ComponentDef.class); DefDescriptor<ComponentDef> parent = definitionService.getDefDescriptor("setAttributesTest:parent", ComponentDef.class); DefDescriptor<ComponentDef> child1 = definitionService.getDefDescriptor("setAttributesTest:child", ComponentDef.class); DefDescriptor<ComponentDef> child2 = definitionService.getDefDescriptor("setAttributesTest:anotherChild", ComponentDef.class); contextService.startContext(AuraContext.Mode.DEV, AuraContext.Format.CSS, AuraContext.Authentication.AUTHENTICATED, appDesc); Set<DefDescriptor<?>> writable = Sets.newLinkedHashSet(); writable.add(definitionService.getDefinition(child1).getStyleDescriptor()); writable.add(definitionService.getDefinition(grandparent).getStyleDescriptor()); writable.add(definitionService.getDefinition(parent).getStyleDescriptor()); writable.add(definitionService.getDefinition(child2).getStyleDescriptor()); StringWriter output = new StringWriter(); ss.writeAppCss(writable, output); String css = output.toString(); // // order should be exactly that above. // child1, grandparent, parent, child2 // assertTrue("child CSS should be written before grandparent CSS in: " + css, css.indexOf(".setAttributesTestChild") < css.indexOf(".setAttributesTestGrandparent")); assertTrue("grandparent CSS should be written before parent CSS in: " + css, css.indexOf(".setAttributesTestGrandparent") < css.indexOf(".setAttributesTestParent")); assertTrue("parent CSS should be written before another child CSS in: " + css, css.indexOf(".setAttributesTestParent") < css.indexOf(".setAttributesTestAnotherChild")); } @Test public void testPreloadCSSDependencies() throws Exception { ServerService ss = serverService; DefDescriptor<ComponentDef> appDesc = definitionService.getDefDescriptor("clientApiTest:cssStyleTest", ComponentDef.class); AuraContext context = contextService.startContext(AuraContext.Mode.DEV, AuraContext.Format.CSS, AuraContext.Authentication.AUTHENTICATED, appDesc); final String uid = context.getDefRegistry().getUid(null, appDesc); context.addLoaded(appDesc, uid); Set<DefDescriptor<?>> dependencies = context.getDefRegistry().getDependencies(uid); StringWriter output = new StringWriter(); ss.writeAppCss(dependencies, output); String sourceNoWhitespace = output.toString().replaceAll("\\s", ""); String preloaded1 = ".clientApiTestCssStyleTest{background-color:#eee}"; String preloaded2 = ".testTestValidCSS{color:#1797c0"; assertTrue("Does not have preloaded css (1) in " + output, sourceNoWhitespace.contains(preloaded1)); assertTrue("Does not have preloaded css (2) in " + output, sourceNoWhitespace.contains(preloaded2)); } /** * Sanity check to make sure that app.js doesn't blow up */ @Test public void testWriteDefinitionsWithoutDupes() throws Exception { ServerService ss = serverService; DefDescriptor<ApplicationDef> appDesc = definitionService.getDefDescriptor("appCache:withpreload", ApplicationDef.class); AuraContext context = contextService.startContext(Mode.DEV, AuraContext.Format.JS, AuraContext.Authentication.AUTHENTICATED, appDesc); final String uid = context.getDefRegistry().getUid(null, appDesc); context.addLoaded(appDesc, uid); Set<DefDescriptor<?>> dependencies = context.getDefRegistry().getDependencies(uid); // prime def cache StringWriter output = new StringWriter(); ss.writeDefinitions(dependencies, output); String text = output.toString(); final String dupeCheck = "$A.clientService.initDefs("; if (text.indexOf(dupeCheck) != text.lastIndexOf(dupeCheck)) { fail("found duplicated code in: " + text); } // now check that defs not re-written with unempty cache output = new StringWriter(); ss.writeDefinitions(dependencies, output); text = output.toString(); if (text.indexOf(dupeCheck) != text.lastIndexOf(dupeCheck)) { fail("found duplicated code in: " + text); } } @Test public void testPreloadJSDependencies() throws Exception { ServerService ss = serverService; DefDescriptor<ComponentDef> appDesc = definitionService.getDefDescriptor("clientApiTest:cssStyleTest", ComponentDef.class); AuraContext context = contextService.startContext(AuraContext.Mode.DEV, AuraContext.Format.JS, AuraContext.Authentication.AUTHENTICATED, appDesc); final String uid = context.getDefRegistry().getUid(null, appDesc); context.addLoaded(appDesc, uid); Set<DefDescriptor<?>> dependencies = context.getDefRegistry().getDependencies(uid); StringWriter output = new StringWriter(); ss.writeDefinitions(dependencies, output); String sourceNoWhitespace = output.toString().replaceAll("\\s", ""); String[] preloads = new String[] { "\"descriptor\":\"markup://aura:placeholder\"", "\"descriptor\":\"markup://ui:input\"", "\"descriptor\":\"markup://ui:inputText\"", "\"descriptor\":\"markup://ui:outputText\"", "\"descriptor\":\"markup://test:testValidCSS\"" }; for (String preload : preloads) { assertTrue("Does not have preloaded component: (" + preload + ")", sourceNoWhitespace.contains(preload)); } } /** * Tests aura definitions has no syntax errors and can be compressed */ @Test public void testNoAppJSCompressionErrors() throws Exception { // check js compression on main aura namespaces String[] namespaces = new String[] { "aura", "ui", "auradev", "auradocs", "auraStorage" }; StringBuilder source = new StringBuilder(); source.append("<aura:application>"); for (String ns : namespaces) { source.append(String.format("<aura:dependency resource=\"%s:*\" type=\"*\" />", ns)); } source.append("</aura:application>"); String js = getDefinitionsOutput(source.toString(), AuraContext.Mode.PROD); assertFalse("There are syntax errors preventing compression of application javascript", js.contains("There are errors preventing this file from being minimized!")); } /** * When we write out the application javascript we should only include component classes once per component. */ // @Test // public void testNoComponentClassDuplicate() throws Exception { // Object componentClass = null; // Boolean found = false; // String js = getDefinitionsOutput( // "<aura:application></aura:application>", AuraContext.Mode.DEV); // assertTrue("aura:html component class not included in app js", // js.contains("addComponentClass(\"markup://aura:html")); // String start = "$A.clientService.initDefs("; // int index = js.indexOf(start); // String componentDefs = js.substring(index + start.length(), // js.length() - 4); // Map<?, ?> json = (Map<?, ?>) new JsonReader().read(componentDefs); // List<?> defs = (List<?>) json.get("componentDefs"); // for (Object def : defs) { // String desc = (String) ((Map<?, ?>) def).get("descriptor"); // if (desc.equals("markup://aura:html")) { // componentClass = ((Map<?, ?>) def).get("componentClass"); // found = true; // } // } // assertTrue("No aura:html componentDef entry found", found); // assertNull( // "Duplicate component class entires for aura:html in application javascript", // componentClass); // } /** * This is verification for W-2657282. The bug was when an IOException is thrown in try block, * a new exception may be thrown in finally block, so the new exception will hide the original * exception. * Verify the original (real) IO exception is thrown from method run. */ @Test public void testThrowsOriginalIOExceptionFromRun() throws Exception { String exceptionMessage = "Test exception"; contextService.startContext(Mode.UTEST, Format.JSON, Authentication.AUTHENTICATED); Writer writer = mock(Writer.class); when(writer.append('{')).thenThrow(new IOException(exceptionMessage)); Message message = new Message(new ArrayList<Action>()); ServerService ss = serverService; try { ss.run(message, contextService.getCurrentContext(), writer, null); fail("Exception should be thrown from method run()."); } catch (IOException e) { assertEquals(exceptionMessage, e.getMessage()); } } private String getDefinitionsOutput(String source, AuraContext.Mode mode) throws Exception { DefDescriptor<ApplicationDef> appDesc = addSourceAutoCleanup(ApplicationDef.class, source); AuraContext context = contextService.startContext(mode, AuraContext.Format.JS, AuraContext.Authentication.AUTHENTICATED, appDesc); final String uid = context.getDefRegistry().getUid(null, appDesc); context.addLoaded(appDesc, uid); Set<DefDescriptor<?>> dependencies = context.getDefRegistry().getDependencies(uid); ServerService ss = serverService; StringWriter output = new StringWriter(); ss.writeDefinitions(dependencies, output); return output.toString(); } /** * Unhandled exceptions such has InterruptedException should set response status to 500 for JS (and CSS) * so it doesn't cache in browser, appcache, etc */ @Test public void testHandleInterruptedException() throws Exception { PrintWriter writer = mock(PrintWriter.class); HttpServletRequest mockRequest = mock(HttpServletRequest.class); HttpServletResponse mockResponse = mock(HttpServletResponse.class); ContextService mockContextService = mock(ContextService.class); AuraContext mockContext = mock(AuraContext.class); ConfigAdapter mockConfigAdapter = mock(ConfigAdapter.class); InstanceStack mockInstanceStack = mock(InstanceStack.class); List<String> stack = Lists.newArrayList(); SerializationService mockSerializationService = mock(SerializationService.class); Mockito.when(mockResponse.getWriter()).thenReturn(writer); // for JS, SC_INTERNAL_SERVER_ERROR Mockito.when(mockContext.getFormat()).thenReturn(AuraContext.Format.JS); Mockito.when(mockContext.getMode()).thenReturn(Mode.PROD); Mockito.when(mockContext.getInstanceStack()).thenReturn(mockInstanceStack); Mockito.when(mockConfigAdapter.isProduction()).thenReturn(true); Mockito.when(mockInstanceStack.getStackInfo()).thenReturn(stack); Mockito.when(mockContextService.getCurrentContext()).thenReturn(mockContext); Throwable exception = new InterruptedException("opps"); ServletUtilAdapterImpl adapter = new ServletUtilAdapterImpl(); adapter.setContextService(mockContextService); adapter.setConfigAdapter(mockConfigAdapter); adapter.setSerializationService(mockSerializationService); adapter.handleServletException(exception, true, mockContext, mockRequest, mockResponse, true); Mockito.verify(mockResponse).setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); Mockito.verify(mockContextService, atLeastOnce()).endContext(); } /** * Verifies first exception within handleServletException is caught and processed * we throw 'EmptyStackException' when getting InstanceStack, then verify * exceptionAdapter.handleException(death) is called with it */ @Test public void testHandleExceptionDeathCaught() throws Exception { PrintWriter writer = mock(PrintWriter.class); HttpServletRequest mockRequest = mock(HttpServletRequest.class); HttpServletResponse mockResponse = mock(HttpServletResponse.class); ContextService mockContextService = mock(ContextService.class); AuraContext mockContext = mock(AuraContext.class); ConfigAdapter mockConfigAdapter = mock(ConfigAdapter.class); ExceptionAdapter mockExceptionAdapter = mock(ExceptionAdapter.class); Throwable firstException = new EmptyStackException(); Mockito.when(mockResponse.getWriter()).thenReturn(writer); Mockito.when(mockContext.getFormat()).thenReturn(AuraContext.Format.JSON); Mockito.when(mockContext.getMode()).thenReturn(Mode.PROD); Mockito.when(mockConfigAdapter.isProduction()).thenReturn(true); Mockito.when(mockContextService.getCurrentContext()).thenReturn(mockContext); Mockito.when(mockContext.getInstanceStack()).thenThrow(firstException); Throwable exception = new InterruptedException("opps"); ServletUtilAdapterImpl adapter = new ServletUtilAdapterImpl(); adapter.setContextService(mockContextService); adapter.setConfigAdapter(mockConfigAdapter); adapter.setExceptionAdapter(mockExceptionAdapter); adapter.handleServletException(exception, true, mockContext, mockRequest, mockResponse, true); ArgumentCaptor<Throwable> handledException = ArgumentCaptor.forClass(Throwable.class); Mockito.verify(mockExceptionAdapter, Mockito.times(1)).handleException(handledException.capture()); assertTrue("Should handle EmptyStackException", handledException.getValue() instanceof EmptyStackException); Mockito.verify(mockResponse, atLeastOnce()).setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); Mockito.verify(mockContextService, atLeastOnce()).endContext(); } /** * Verifies second exception within handleServletException is caught and processed * we throw 'EmptyStackException' when getting InstanceStack, when * exceptionAdapter.handleException(death) handle the exception, * we throw second exception, then verify we printout the error message to response's writer */ @Test public void testHandleExceptionDoubleDeathCaught() throws Exception { PrintWriter writer = mock(PrintWriter.class); HttpServletRequest mockRequest = mock(HttpServletRequest.class); HttpServletResponse mockResponse = mock(HttpServletResponse.class); ContextService mockContextService = mock(ContextService.class); AuraContext mockContext = mock(AuraContext.class); ConfigAdapter mockConfigAdapter = mock(ConfigAdapter.class); ExceptionAdapter mockExceptionAdapter = mock(ExceptionAdapter.class); Throwable firstException = new EmptyStackException(); String ccmeMsg = "double dead"; Throwable secondException = new ConcurrentModificationException("double dead"); Mockito.when(mockResponse.getWriter()).thenReturn(writer); Mockito.when(mockContext.getFormat()).thenReturn(AuraContext.Format.HTML); Mockito.when(mockContext.getMode()).thenReturn(Mode.DEV); Mockito.when(mockConfigAdapter.isProduction()).thenReturn(false); Mockito.when(mockContextService.getCurrentContext()).thenReturn(mockContext); Mockito.when(mockContext.getInstanceStack()).thenThrow(firstException); Mockito.when(mockExceptionAdapter.handleException(firstException)).thenThrow(secondException); Throwable exception = new InterruptedException("opps"); ServletUtilAdapterImpl adapter = new ServletUtilAdapterImpl(); adapter.setContextService(mockContextService); adapter.setConfigAdapter(mockConfigAdapter); adapter.setExceptionAdapter(mockExceptionAdapter); adapter.handleServletException(exception, true, mockContext, mockRequest, mockResponse, true); ArgumentCaptor<String> exceptionMessage = ArgumentCaptor.forClass(String.class); Mockito.verify(writer, Mockito.times(1)).println(exceptionMessage.capture()); assertEquals(ccmeMsg, exceptionMessage.getValue()); Mockito.verify(mockResponse, atLeastOnce()).setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); Mockito.verify(mockContextService, atLeastOnce()).endContext(); } }