com.gargoylesoftware.htmlunit.CacheTest.java Source code

Java tutorial

Introduction

Here is the source code for com.gargoylesoftware.htmlunit.CacheTest.java

Source

/*
 * Copyright (c) 2002-2016 Gargoyle Software 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 com.gargoylesoftware.htmlunit;

import static com.gargoylesoftware.htmlunit.util.StringUtils.formatHttpDate;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;

import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.time.DateUtils;
import org.junit.Test;
import org.junit.runner.RunWith;

import com.gargoylesoftware.htmlunit.BrowserRunner.Alerts;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import com.gargoylesoftware.htmlunit.util.StringUtils;

/**
 * Tests for {@link Cache}.
 *
 * @author Marc Guillemot
 * @author Ahmed Ashour
 * @author Frank Danek
 */
@RunWith(BrowserRunner.class)
public class CacheTest extends SimpleWebTestCase {

    /**
     * @throws Exception if the test fails
     */
    @Test
    public void isCacheableContent() throws Exception {
        final Cache cache = new Cache();
        final Map<String, String> headers = new HashMap<>();
        final WebResponse response = new DummyWebResponse() {
            @Override
            public String getResponseHeaderValue(final String headerName) {
                return headers.get(headerName);
            }
        };

        assertFalse(cache.isCacheableContent(response));

        headers.put("Last-Modified", "Sun, 15 Jul 2007 20:46:27 GMT");
        assertTrue(cache.isCacheableContent(response));

        headers.put("Last-Modified", formatHttpDate(DateUtils.addMinutes(new Date(), -5)));
        assertFalse(cache.isCacheableContent(response));

        headers.put("Expires", formatHttpDate(DateUtils.addMinutes(new Date(), 5)));
        assertFalse(cache.isCacheableContent(response));

        headers.put("Expires", formatHttpDate(DateUtils.addHours(new Date(), 1)));
        assertTrue(cache.isCacheableContent(response));

        headers.remove("Last-Modified");
        assertTrue(cache.isCacheableContent(response));

        headers.put("Expires", "0");
        assertFalse(cache.isCacheableContent(response));

        headers.put("Expires", "-1");
        assertFalse(cache.isCacheableContent(response));
    }

    /**
     *@throws Exception if the test fails
     */
    @Test
    public void usage() throws Exception {
        final String content = "<html><head><title>page 1</title>\n" + "<script src='foo1.js'></script>\n"
                + "<script src='foo2.js'></script>\n" + "</head><body>\n" + "<a href='page2.html'>to page 2</a>\n"
                + "</body></html>";

        final String content2 = "<html><head><title>page 2</title>\n" + "<script src='foo2.js'></script>\n"
                + "</head><body>\n" + "<a href='page1.html'>to page 1</a>\n" + "</body></html>";

        final String script1 = "alert('in foo1');";
        final String script2 = "alert('in foo2');";

        final WebClient webClient = getWebClient();
        final MockWebConnection connection = new MockWebConnection();
        webClient.setWebConnection(connection);

        final URL urlPage1 = new URL(URL_FIRST, "page1.html");
        connection.setResponse(urlPage1, content);
        final URL urlPage2 = new URL(URL_FIRST, "page2.html");
        connection.setResponse(urlPage2, content2);

        final List<NameValuePair> headers = new ArrayList<>();
        headers.add(new NameValuePair("Last-Modified", "Sun, 15 Jul 2007 20:46:27 GMT"));
        connection.setResponse(new URL(URL_FIRST, "foo1.js"), script1, 200, "ok", JAVASCRIPT_MIME_TYPE, headers);
        connection.setResponse(new URL(URL_FIRST, "foo2.js"), script2, 200, "ok", JAVASCRIPT_MIME_TYPE, headers);

        final List<String> collectedAlerts = new ArrayList<>();
        webClient.setAlertHandler(new CollectingAlertHandler(collectedAlerts));

        final HtmlPage page1 = webClient.getPage(urlPage1);
        final String[] expectedAlerts = { "in foo1", "in foo2" };
        assertEquals(expectedAlerts, collectedAlerts);

        collectedAlerts.clear();
        page1.getAnchors().get(0).click();

        assertEquals(new String[] { "in foo2" }, collectedAlerts);
        assertEquals("no request for scripts should have been performed", urlPage2,
                connection.getLastWebRequest().getUrl());
    }

    /**
     *@throws Exception if the test fails
     */
    @Test
    public void jsUrlEncoded() throws Exception {
        final String content = "<html>\n" + "<head>\n" + "  <title>page 1</title>\n"
                + "  <script src='foo1.js'></script>\n" + "  <script src='foo2.js?foo[1]=bar/baz'></script>\n"
                + "</head>\n" + "<body>\n" + "  <a href='page2.html'>to page 2</a>\n" + "</body>\n" + "</html>";

        final String content2 = "<html>\n" + "<head>\n" + "  <title>page 2</title>\n"
                + "  <script src='foo2.js?foo[1]=bar/baz'></script>\n" + "</head>\n" + "<body>\n"
                + "  <a href='page1.html'>to page 1</a>\n" + "</body>\n" + "</html>";

        final String script1 = "alert('in foo1');";
        final String script2 = "alert('in foo2');";

        final URL urlPage1 = new URL(URL_FIRST, "page1.html");
        getMockWebConnection().setResponse(urlPage1, content);
        final URL urlPage2 = new URL(URL_FIRST, "page2.html");
        getMockWebConnection().setResponse(urlPage2, content2);

        final List<NameValuePair> headers = new ArrayList<>();
        headers.add(new NameValuePair("Last-Modified", "Sun, 15 Jul 2007 20:46:27 GMT"));
        getMockWebConnection().setResponse(new URL(URL_FIRST, "foo1.js"), script1, 200, "ok", JAVASCRIPT_MIME_TYPE,
                headers);
        getMockWebConnection().setDefaultResponse(script2, 200, "ok", JAVASCRIPT_MIME_TYPE, headers);

        final WebClient webClient = getWebClientWithMockWebConnection();

        final List<String> collectedAlerts = new ArrayList<>();
        webClient.setAlertHandler(new CollectingAlertHandler(collectedAlerts));

        final HtmlPage page1 = webClient.getPage(urlPage1);
        final String[] expectedAlerts = { "in foo1", "in foo2" };
        assertEquals(expectedAlerts, collectedAlerts);

        collectedAlerts.clear();
        page1.getAnchors().get(0).click();

        assertEquals(new String[] { "in foo2" }, collectedAlerts);
        assertEquals("no request for scripts should have been performed", urlPage2,
                getMockWebConnection().getLastWebRequest().getUrl());
    }

    /**
     *@throws Exception if the test fails
     */
    @Test
    public void cssUrlEncoded() throws Exception {
        final String content = "<html>\n" + "<head>\n" + "  <title>page 1</title>\n"
                + "  <link href='foo1.css' type='text/css' rel='stylesheet'>\n"
                + "  <link href='foo2.js?foo[1]=bar/baz' type='text/css' rel='stylesheet'>\n" + "</head>\n"
                + "<body>\n" + "  <a href='page2.html'>to page 2</a>\n" + "  <script>\n"
                + "    var sheets = document.styleSheets;\n" + "    alert(sheets.length);\n"
                + "    var rules = sheets[0].cssRules || sheets[0].rules;\n" + "    alert(rules.length);\n"
                + "    rules = sheets[1].cssRules || sheets[1].rules;\n" + "    alert(rules.length);\n"
                + "  </script>\n" + "</body>\n" + "</html>";

        final String content2 = "<html>\n" + "<head>\n" + "  <title>page 2</title>\n"
                + "  <link href='foo2.js?foo[1]=bar/baz' type='text/css' rel='stylesheet'>\n" + "</head>\n"
                + "<body>\n" + "  <a href='page1.html'>to page 1</a>\n" + "  <script>\n"
                + "    var sheets = document.styleSheets;\n" + "    alert(sheets.length);\n"
                + "    var rules = sheets[0].cssRules || sheets[0].rules;\n" + "    alert(rules.length);\n"
                + "  </script>\n" + "</body>\n" + "</html>";

        final URL urlPage1 = new URL(URL_FIRST, "page1.html");
        getMockWebConnection().setResponse(urlPage1, content);
        final URL urlPage2 = new URL(URL_FIRST, "page2.html");
        getMockWebConnection().setResponse(urlPage2, content2);

        final List<NameValuePair> headers = new ArrayList<>();
        headers.add(new NameValuePair("Last-Modified", "Sun, 15 Jul 2007 20:46:27 GMT"));
        getMockWebConnection().setResponse(new URL(URL_FIRST, "foo1.js"), "", 200, "ok", "text/css", headers);
        getMockWebConnection().setDefaultResponse("", 200, "ok", "text/css", headers);

        final WebClient webClient = getWebClientWithMockWebConnection();

        final List<String> collectedAlerts = new ArrayList<>();
        webClient.setAlertHandler(new CollectingAlertHandler(collectedAlerts));

        final HtmlPage page1 = webClient.getPage(urlPage1);
        final String[] expectedAlerts = { "2", "0", "0" };
        assertEquals(expectedAlerts, collectedAlerts);
        assertEquals(3, getMockWebConnection().getRequestCount());

        collectedAlerts.clear();
        page1.getAnchors().get(0).click();

        assertEquals(new String[] { "1", "0" }, collectedAlerts);
        assertEquals(4, getMockWebConnection().getRequestCount());
        assertEquals("no request for scripts should have been performed", urlPage2,
                getMockWebConnection().getLastWebRequest().getUrl());
    }

    /**
     *@throws Exception if the test fails
     */
    @Test
    public void maxSizeMaintained() throws Exception {
        final String html = "<html><head><title>page 1</title>\n"
                + "<script src='foo1.js' type='text/javascript'/>\n"
                + "<script src='foo2.js' type='text/javascript'/>\n" + "</head><body>abc</body></html>";

        final WebClient client = getWebClient();
        client.getCache().setMaxSize(1);

        final MockWebConnection connection = new MockWebConnection();
        client.setWebConnection(connection);

        final URL pageUrl = new URL(URL_FIRST, "page1.html");
        connection.setResponse(pageUrl, html);

        final List<NameValuePair> headers = Collections
                .singletonList(new NameValuePair("Last-Modified", "Sun, 15 Jul 2007 20:46:27 GMT"));
        connection.setResponse(new URL(URL_FIRST, "foo1.js"), ";", 200, "ok", JAVASCRIPT_MIME_TYPE, headers);
        connection.setResponse(new URL(URL_FIRST, "foo2.js"), ";", 200, "ok", JAVASCRIPT_MIME_TYPE, headers);

        client.getPage(pageUrl);
        assertEquals(1, client.getCache().getSize());

        client.getCache().clear();
        assertEquals(0, client.getCache().getSize());
    }

    /**
     * TODO: improve CSS caching to cache a COPY of the object as stylesheet objects can be modified dynamically.
     * @throws Exception if the test fails
     */
    @Test
    public void cssIsCached() throws Exception {
        final String html = "<html><head><title>page 1</title>\n" + "<style>.x { color: red; }</style>\n"
                + "<link rel='stylesheet' type='text/css' href='foo.css' />\n" + "</head>\n"
                + "<body onload='document.styleSheets.item(0); document.styleSheets.item(1);'>x</body>\n"
                + "</html>";

        final WebClient client = getWebClient();

        final MockWebConnection connection = new MockWebConnection();
        client.setWebConnection(connection);

        final URL pageUrl = new URL(URL_FIRST, "page1.html");
        connection.setResponse(pageUrl, html);

        final List<NameValuePair> headers = Collections
                .singletonList(new NameValuePair("Last-Modified", "Sun, 15 Jul 2007 20:46:27 GMT"));
        connection.setResponse(new URL(URL_FIRST, "foo.css"), "", 200, "OK", JAVASCRIPT_MIME_TYPE, headers);

        client.getPage(pageUrl);
        assertEquals(2, client.getCache().getSize());
    }

    /**
     * Test that content retrieved with XHR is cached when right headers are here.
     * @throws Exception if the test fails
     */
    @Test
    @Alerts({ "hello", "hello" })
    public void xhrContentCached() throws Exception {
        final String html = "<html><head><title>page 1</title>\n" + "<script>\n" + "  function doTest() {\n"
                + "    var xhr = window.XMLHttpRequest ? new XMLHttpRequest()"
                + "      : new ActiveXObject('Microsoft.XMLHTTP');\n" + "    xhr.open('GET', 'foo.txt', false);\n"
                + "    xhr.send('');\n" + "    alert(xhr.responseText);\n" + "    xhr.send('');\n"
                + "    alert(xhr.responseText);\n" + "  }\n" + "</script>\n" + "</head>\n"
                + "<body onload='doTest()'>x</body>\n" + "</html>";

        final MockWebConnection connection = getMockWebConnection();

        final List<NameValuePair> headers = Collections
                .singletonList(new NameValuePair("Last-Modified", "Sun, 15 Jul 2007 20:46:27 GMT"));
        connection.setResponse(new URL(getDefaultUrl(), "foo.txt"), "hello", 200, "OK", "text/plain", headers);

        loadPageWithAlerts(html);

        assertEquals(2, connection.getRequestCount());
    }

    /**
     * Ensures {@link WebResponse#cleanUp()} is called for overflow deleted entries.
     * @throws Exception if the test fails
     */
    @Test
    public void cleanUpOverflow() throws Exception {
        final WebRequest request1 = new WebRequest(URL_FIRST, HttpMethod.GET);
        final WebResponse response1 = createMock(WebResponse.class);
        expect(response1.getWebRequest()).andReturn(request1);
        expectLastCall().atLeastOnce();
        expect(response1.getResponseHeaderValue("Last-Modified")).andReturn(null);
        expect(response1.getResponseHeaderValue("Expires"))
                .andReturn(StringUtils.formatHttpDate(DateUtils.addHours(new Date(), 1)));

        final WebRequest request2 = new WebRequest(URL_SECOND, HttpMethod.GET);
        final WebResponse response2 = createMock(WebResponse.class);
        expect(response2.getWebRequest()).andReturn(request2);
        expectLastCall().atLeastOnce();
        expect(response2.getResponseHeaderValue("Last-Modified")).andReturn(null);
        expect(response2.getResponseHeaderValue("Expires"))
                .andReturn(StringUtils.formatHttpDate(DateUtils.addHours(new Date(), 1)));

        response1.cleanUp();

        replay(response1, response2);

        final Cache cache = new Cache();
        cache.setMaxSize(1);
        cache.cacheIfPossible(request1, response1, null);
        Thread.sleep(10);
        cache.cacheIfPossible(request2, response2, null);

        verify(response1);
    }

    /**
     * Ensures {@link WebResponse#cleanUp()} is called on calling {@link Cache#clear()}.
     * @throws Exception if the test fails
     */
    @Test
    public void cleanUpOnClear() throws Exception {
        final WebRequest request1 = new WebRequest(URL_FIRST, HttpMethod.GET);
        final WebResponse response1 = createMock(WebResponse.class);
        expect(response1.getWebRequest()).andReturn(request1);
        expectLastCall().atLeastOnce();
        expect(response1.getResponseHeaderValue("Last-Modified")).andReturn(null);
        expect(response1.getResponseHeaderValue("Expires"))
                .andReturn(StringUtils.formatHttpDate(DateUtils.addHours(new Date(), 1)));

        response1.cleanUp();

        replay(response1);

        final Cache cache = new Cache();
        cache.cacheIfPossible(request1, response1, null);

        cache.clear();

        verify(response1);
    }
}

class DummyWebResponse extends WebResponse {

    DummyWebResponse() {
        super(null, null, 0);
    }

    @Override
    public InputStream getContentAsStream() {
        throw new RuntimeException("not implemented");
    }

    @Override
    public String getContentAsString() {
        throw new RuntimeException("not implemented");
    }

    @Override
    public String getContentAsString(final String encoding) {
        throw new RuntimeException("not implemented");
    }

    @Override
    public String getContentCharset() {
        throw new RuntimeException("not implemented");
    }

    @Override
    public String getContentCharsetOrNull() {
        throw new RuntimeException("not implemented");
    }

    @Override
    public String getContentType() {
        throw new RuntimeException("not implemented");
    }

    @Override
    public long getLoadTime() {
        throw new RuntimeException("not implemented");
    }

    @Override
    public List<NameValuePair> getResponseHeaders() {
        throw new RuntimeException("not implemented");
    }

    @Override
    public String getResponseHeaderValue(final String headerName) {
        throw new RuntimeException("not implemented");
    }

    @Override
    public int getStatusCode() {
        throw new RuntimeException("not implemented");
    }

    @Override
    public String getStatusMessage() {
        throw new RuntimeException("not implemented");
    }

    @Override
    public WebRequest getWebRequest() {
        throw new RuntimeException("not implemented");
    }
}