/* * Copyright 2002-2016 the original author or authors. * * 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.springframework.web.servlet.view; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Properties; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.Test; import org.springframework.context.ApplicationContextException; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.mock.web.test.MockServletContext; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.View; import static org.junit.Assert.*; import static org.mockito.BDDMockito.*; /** * Base tests for {@link AbstractView}. * * <p>Not called {@code AbstractViewTests} since doing so would cause it * to be ignored in the Gradle build. * * @author Rod Johnson * @author Sam Brannen */ public class BaseViewTests { @Test public void renderWithoutStaticAttributes() throws Exception { WebApplicationContext wac = mock(WebApplicationContext.class); given(wac.getServletContext()).willReturn(new MockServletContext()); HttpServletRequest request = new MockHttpServletRequest(); HttpServletResponse response = new MockHttpServletResponse(); TestView tv = new TestView(wac); // Check superclass handles duplicate init tv.setApplicationContext(wac); tv.setApplicationContext(wac); Map<String, Object> model = new HashMap<>(); model.put("foo", "bar"); model.put("something", new Object()); tv.render(model, request, response); checkContainsAll(model, tv.model); assertTrue(tv.initialized); } /** * Test attribute passing, NOT CSV parsing. */ @Test public void renderWithStaticAttributesNoCollision() throws Exception { WebApplicationContext wac = mock(WebApplicationContext.class); given(wac.getServletContext()).willReturn(new MockServletContext()); HttpServletRequest request = new MockHttpServletRequest(); HttpServletResponse response = new MockHttpServletResponse(); TestView tv = new TestView(wac); tv.setApplicationContext(wac); Properties p = new Properties(); p.setProperty("foo", "bar"); p.setProperty("something", "else"); tv.setAttributes(p); Map<String, Object> model = new HashMap<>(); model.put("one", new HashMap<>()); model.put("two", new Object()); tv.render(model, request, response); checkContainsAll(model, tv.model); checkContainsAll(p, tv.model); assertTrue(tv.initialized); } @Test public void pathVarsOverrideStaticAttributes() throws Exception { WebApplicationContext wac = mock(WebApplicationContext.class); given(wac.getServletContext()).willReturn(new MockServletContext()); HttpServletRequest request = new MockHttpServletRequest(); HttpServletResponse response = new MockHttpServletResponse(); TestView tv = new TestView(wac); tv.setApplicationContext(wac); Properties p = new Properties(); p.setProperty("one", "bar"); p.setProperty("something", "else"); tv.setAttributes(p); Map<String, Object> pathVars = new HashMap<>(); pathVars.put("one", new HashMap<>()); pathVars.put("two", new Object()); request.setAttribute(View.PATH_VARIABLES, pathVars); tv.render(new HashMap<>(), request, response); checkContainsAll(pathVars, tv.model); assertEquals(3, tv.model.size()); assertEquals("else", tv.model.get("something")); assertTrue(tv.initialized); } @Test public void dynamicModelOverridesStaticAttributesIfCollision() throws Exception { WebApplicationContext wac = mock(WebApplicationContext.class); given(wac.getServletContext()).willReturn(new MockServletContext()); HttpServletRequest request = new MockHttpServletRequest(); HttpServletResponse response = new MockHttpServletResponse(); TestView tv = new TestView(wac); tv.setApplicationContext(wac); Properties p = new Properties(); p.setProperty("one", "bar"); p.setProperty("something", "else"); tv.setAttributes(p); Map<String, Object> model = new HashMap<>(); model.put("one", new HashMap<>()); model.put("two", new Object()); tv.render(model, request, response); // Check it contains all checkContainsAll(model, tv.model); assertEquals(3, tv.model.size()); assertEquals("else", tv.model.get("something")); assertTrue(tv.initialized); } @Test public void dynamicModelOverridesPathVariables() throws Exception { WebApplicationContext wac = mock(WebApplicationContext.class); given(wac.getServletContext()).willReturn(new MockServletContext()); TestView tv = new TestView(wac); tv.setApplicationContext(wac); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); Map<String, Object> pathVars = new HashMap<>(); pathVars.put("one", "bar"); pathVars.put("something", "else"); request.setAttribute(View.PATH_VARIABLES, pathVars); Map<String, Object> model = new HashMap<>(); model.put("one", new HashMap<>()); model.put("two", new Object()); tv.render(model, request, response); checkContainsAll(model, tv.model); assertEquals(3, tv.model.size()); assertEquals("else", tv.model.get("something")); assertTrue(tv.initialized); } @Test public void ignoresNullAttributes() { AbstractView v = new ConcreteView(); v.setAttributes(null); assertEquals(0, v.getStaticAttributes().size()); } /** * Test only the CSV parsing implementation. */ @Test public void attributeCSVParsingIgnoresNull() { AbstractView v = new ConcreteView(); v.setAttributesCSV(null); assertEquals(0, v.getStaticAttributes().size()); } @Test public void attributeCSVParsingIgnoresEmptyString() { AbstractView v = new ConcreteView(); v.setAttributesCSV(""); assertEquals(0, v.getStaticAttributes().size()); } /** * Format is attname0={value1},attname1={value1} */ @Test public void attributeCSVParsingValid() { AbstractView v = new ConcreteView(); v.setAttributesCSV("foo=[bar],king=[kong]"); assertTrue(v.getStaticAttributes().size() == 2); assertTrue(v.getStaticAttributes().get("foo").equals("bar")); assertTrue(v.getStaticAttributes().get("king").equals("kong")); } @Test public void attributeCSVParsingValidWithWeirdCharacters() { AbstractView v = new ConcreteView(); String fooval = "owfie fue&3[][[[2 \n\n \r \t 8\ufffd3"; // Also tests empty value String kingval = ""; v.setAttributesCSV("foo=(" + fooval + "),king={" + kingval + "},f1=[we]"); assertTrue(v.getStaticAttributes().size() == 3); assertTrue(v.getStaticAttributes().get("foo").equals(fooval)); assertTrue(v.getStaticAttributes().get("king").equals(kingval)); } @Test public void attributeCSVParsingInvalid() { AbstractView v = new ConcreteView(); try { // No equals v.setAttributesCSV("fweoiruiu"); fail(); } catch (IllegalArgumentException ex) { } try { // No value v.setAttributesCSV("fweoiruiu="); fail(); } catch (IllegalArgumentException ex) { } try { // No closing ] v.setAttributesCSV("fweoiruiu=["); fail(); } catch (IllegalArgumentException ex) { } try { // Second one is bogus v.setAttributesCSV("fweoiruiu=[de],="); fail(); } catch (IllegalArgumentException ex) { } } @Test public void attributeCSVParsingIgoresTrailingComma() { AbstractView v = new ConcreteView(); v.setAttributesCSV("foo=[de],"); assertEquals(1, v.getStaticAttributes().size()); } /** * Check that all keys in expected have same values in actual. */ @SuppressWarnings({ "rawtypes", "unchecked" }) private void checkContainsAll(Map expected, Map<String, Object> actual) { expected.keySet().stream().forEach( key -> assertEquals("Values for model key '" + key + "' must match", expected.get(key), actual.get(key)) ); } /** * Trivial concrete subclass we can use when we're interested only * in CSV parsing, which doesn't require lifecycle management */ private static class ConcreteView extends AbstractView { // Do-nothing concrete subclass @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { throw new UnsupportedOperationException(); } } /** * Single threaded subclass of AbstractView to check superclass behavior. */ private static class TestView extends AbstractView { private final WebApplicationContext wac; boolean initialized; /** Captured model in render */ Map<String, Object> model; TestView(WebApplicationContext wac) { this.wac = wac; } @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.model = model; } /** * @see org.springframework.context.support.ApplicationObjectSupport#initApplicationContext() */ @Override protected void initApplicationContext() throws ApplicationContextException { if (initialized) { throw new RuntimeException("Already initialized"); } this.initialized = true; assertTrue(getApplicationContext() == wac); } } }