View Javadoc
1   /**
2    * This file Copyright (c) 2013-2015 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.testframework.htmlunit;
35  
36  import static org.junit.Assert.*;
37  
38  import info.magnolia.testframework.AbstractMagnoliaIntegrationTest;
39  
40  import java.io.FileOutputStream;
41  import java.io.IOException;
42  import java.io.InputStream;
43  import java.net.HttpURLConnection;
44  import java.net.URL;
45  import java.util.Collections;
46  import java.util.HashMap;
47  import java.util.Map;
48  
49  import org.apache.commons.codec.binary.Base64;
50  import org.apache.commons.io.IOUtils;
51  
52  import com.gargoylesoftware.htmlunit.BrowserVersion;
53  import com.gargoylesoftware.htmlunit.Page;
54  import com.gargoylesoftware.htmlunit.WebClient;
55  import com.gargoylesoftware.htmlunit.WebConnection;
56  import com.gargoylesoftware.htmlunit.WebResponse;
57  import com.gargoylesoftware.htmlunit.html.HtmlPage;
58  import com.gargoylesoftware.htmlunit.util.DebuggingWebConnection;
59  
60  /**
61   * A base class for Magnolia integration tests. Might be split into util class/methods;
62   * since we use JUnit4, inheritance isn't really mandatory.
63   */
64  public abstract class AbstractMagnoliaHtmlUnitTest extends AbstractMagnoliaIntegrationTest{
65  
66      /**
67       * Session id's can consist of any digit and letter (lower or upper case).
68       */
69      protected static final String SESSION_ID_REGEXP = ";jsessionid=[a-zA-Z0-9]+";
70  
71      protected static final Map<String, String> DEFAULT_HEADERS = new HashMap<String, String>() {{
72          put("Referer", Instance.AUTHOR.getURL(""));
73      }};
74  
75      /**
76       * @see #openConnection(AbstractMagnoliaHtmlUnitTest.Instance, String, AbstractMagnoliaHtmlUnitTest.User, java.util.Map)
77       */
78      protected HttpURLConnection openConnection(Instance instance, String path, User user) throws IOException {
79          return openConnection(instance, path, user, Collections.<String, String>emptyMap());
80      }
81  
82      /**
83       * Use this method when you need low-level access to the connection headers and content.
84       */
85      protected HttpURLConnection openConnection(Instance instance, String path, User user, Map<String, String> headers) throws IOException {
86          final URL url = new URL(instance.getURL(path));
87          final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
88          if (user != null) {
89              final String authValue = getAuthValue(user.name());
90              connection.setRequestProperty("Authorization", authValue);
91          }
92  
93          for (String header : headers.keySet()) {
94              connection.setRequestProperty(header, headers.get(header));
95          }
96  
97          connection.connect();
98          return connection;
99      }
100 
101     /**
102      * Just a shortcut method to avoid a cast to HtmlPage.
103      * @see #openPage(AbstractMagnoliaHtmlUnitTest.Instance, String, AbstractMagnoliaHtmlUnitTest.User, boolean)
104      * @deprecated openPage now uses generics, so use that instead.
105      */
106     @Deprecated
107     protected HtmlPage openHtmlPage(Instance instance, String path, User user) throws IOException {
108         return openHtmlPage(instance, path, user, false);
109     }
110 
111     /**
112      * @deprecated openPage now uses generics, so use that instead.
113      */
114     @Deprecated
115     protected HtmlPage openHtmlPage(Instance instance, String path, User user, boolean followRedirects) throws IOException {
116         return (HtmlPage) openPage(instance, path, user, followRedirects);
117     }
118 
119     /**
120      * @deprecated use {@link #openPage(String, AbstractMagnoliaHtmlUnitTest.User, boolean)}
121      * with {@link AbstractMagnoliaHtmlUnitTest.Instance.AUTHOR.getURL()}
122      */
123     @Deprecated
124     protected Page openPage(Instance instance, String path, User user) throws IOException {
125         return openPage(instance, path, user, false);
126     }
127 
128     /**
129      * @deprecated use {@link #openPage(String, AbstractMagnoliaHtmlUnitTest.User, boolean)}
130      * with {@link AbstractMagnoliaIntegrationTest.Instance.AUTHOR.getURL()}
131      */
132     @Deprecated
133     protected <P extends Page> P openPage(Instance instance, String path, User user, boolean followRedirects) throws IOException {
134         return (P) openPage(instance.getURL(path), user, followRedirects, true, DEFAULT_HEADERS);
135     }
136 
137     protected <P extends Page> P openPage(String url, User user) throws IOException {
138         return (P) openPage(url, user, false, true, DEFAULT_HEADERS);
139     }
140 
141     protected <P extends Page> P openPage(String url, User user, boolean followRedirects) throws IOException {
142         return (P) openPage(url, user, followRedirects, true, DEFAULT_HEADERS);
143     }
144 
145     protected <P extends Page> P openPage(String url, User user, boolean followRedirects, boolean enableJavascript) throws IOException {
146         return (P) openPage(url, user, followRedirects, enableJavascript, DEFAULT_HEADERS);
147     }
148 
149     /**
150      * This uses htmlunit, simulates a browser and does all kind of fancy stuff for you.
151      */
152     protected <P extends Page> P openPage(String url, User user, boolean followRedirects, boolean enableJavascript, Map<String, String> headers) throws IOException {
153         final WebClient webClient = new WebClient(BrowserVersion.FIREFOX_24);
154         // this writes files to /tmp - the most interesting one probably being magnolia-test_<random>.js, which lists headers for all requests
155         final WebConnection connection = new DebuggingWebConnection(webClient.getWebConnection(), "magnolia-test_");
156         webClient.setWebConnection(connection);
157 
158         webClient.getOptions().setRedirectEnabled(followRedirects);
159 
160         // we also want to test error code handling:
161         webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
162 
163         webClient.getOptions().setCssEnabled(true);
164         webClient.getOptions().setJavaScriptEnabled(enableJavascript);
165 
166         // add custom headers to the client
167         for (String header : headers.keySet()) {
168             webClient.addRequestHeader(header, headers.get(header));
169         }
170 
171         if (user != null) {
172             final String authValue = getAuthValue(user.name());
173             webClient.addRequestHeader("Authorization", authValue);
174         }
175 
176         return (P) webClient.getPage(new URL(url));
177     }
178 
179     protected <P extends Page> P assertRedirected(String reason, String expectedTargetURLPattern, Page page, User user) throws IOException {
180         assertEquals(302, page.getWebResponse().getStatusCode());
181         final String location = page.getWebResponse().getResponseHeaderValue("Location");
182         // only test whether it has the proper start in order to ignore e.g. attached sessionIds
183         assertTrue("Redirect location " + location + " does not match the expected pattern: " + expectedTargetURLPattern + " ("+reason+")", location.matches(expectedTargetURLPattern));
184 
185         // since this is already redirected do not follow more redirects
186         // also do not execute javascript on the target page - at least not until https://sourceforge.net/tracker/?func=detail&aid=3110090&group_id=47038&atid=448266 is solved
187         return (P) openPage(location, user, false, false, DEFAULT_HEADERS);
188     }
189 
190     /**
191      * Passing a fake exception might be simpler for some cases.
192      * @see #saveToFile(com.gargoylesoftware.htmlunit.Page, StackTraceElement)
193      */
194     protected void saveToFile(Page page, Throwable fakeException) throws IOException {
195         saveToFile(page, fakeException.getStackTrace()[0]);
196     }
197 
198     /**
199      * Need to pass a StackTraceElement to determine the
200      * current method, because we can't safely guess at what depth of the stack this method was called.
201      * We're keeping this method separate from openPage() for the same reason: if a test/util method
202      * calls the openPage method instead of the actual test, the stack won't reflect the "current test method"
203      * properly.
204      */
205     protected void saveToFile(Page page, StackTraceElement stackTraceElement) throws IOException {
206         final WebResponse res = page.getWebResponse();
207         InputStream input = res.getContentAsStream();
208         final byte[] body = IOUtils.toByteArray(input);
209         // TODO : configure the output directory / get it from system properties ?
210         final String path = "target/" + stackTraceElement.getClassName() + "-" + stackTraceElement.getMethodName() + "-" + stackTraceElement.getLineNumber() + ".out";
211         IOUtils.write(body, new FileOutputStream(path));
212     }
213 
214     /**
215      * Sets a system property (using the SystemPropertyServlet servlet) to a given value. Returns its previous value.
216      */
217     protected String setSystemProperty(Instance instance, String name, String value) throws IOException {
218         HtmlPage page = openPage(instance.getURL("/.magnolia/sysprop/?name=" + name + "&value=" + value), User.superuser);
219         assertEquals(200, page.getWebResponse().getStatusCode());
220         return page.asText();
221     }
222 
223     /**
224      * Sample users have an identical username and password !
225      */
226     private String getAuthValue(String usernameAndPassword) {
227         final String authString = usernameAndPassword + ":" + usernameAndPassword;
228         final String encodedAuthStr = new String(Base64.encodeBase64(authString.getBytes()));
229         return "Basic " + encodedAuthStr;
230     }
231 }