Package org.archive.wayback.webapp

Source Code of org.archive.wayback.webapp.AccessPointTest

/**
*
*/
package org.archive.wayback.webapp;

import java.io.IOException;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import junit.framework.TestCase;

import org.archive.format.ArchiveFileConstants;
import org.archive.io.ArchiveRecordHeader;
import org.archive.io.warc.TestWARCReader;
import org.archive.io.warc.TestWARCRecordInfo;
import org.archive.io.warc.WARCRecord;
import org.archive.wayback.QueryRenderer;
import org.archive.wayback.ReplayDispatcher;
import org.archive.wayback.ReplayRenderer;
import org.archive.wayback.RequestParser;
import org.archive.wayback.ResourceIndex;
import org.archive.wayback.ResourceStore;
import org.archive.wayback.archivalurl.ArchivalUrlResultURIConverter;
import org.archive.wayback.core.CaptureSearchResult;
import org.archive.wayback.core.CaptureSearchResults;
import org.archive.wayback.core.Resource;
import org.archive.wayback.core.UrlSearchResults;
import org.archive.wayback.core.WaybackRequest;
import org.archive.wayback.memento.MementoUtils;
import org.archive.wayback.resourcestore.resourcefile.ArcResource;
import org.archive.wayback.resourcestore.resourcefile.WarcResource;
import org.archive.wayback.util.webapp.RequestMapper;
import org.easymock.EasyMock;
import org.easymock.IArgumentMatcher;

/**
* unit test for {@link AccessPoint}.
*
* TODO: this unit test is too complex. it is because
* AccessPoint class has too much responsibility and many execution paths.
* some good refactoring of AccessPoint class would help.
*
* @author Kenji Nagahashi
*
*/
public class AccessPointTest extends TestCase {

    AccessPoint cut;
   
    // AccessPoint public interface
    // init()
    // handleRequest()
    // queryIndex(WaybackRequest) - actually it's only used internally. it's public just because
    //   LiveWebAccessPoint is reusing it. LiveWebAccessPoint also calls getReplay() for rendering
    //   resource in accordance with rendering mode configured.
    // shutdown()
   
    // dependencies
    // selfRedirectCanonicalizer: UrlCanonicalizer
    // filterFactory: CustomResultFilterFactory
    // exclusionFactory: ExclusionFilterFactory
    // uriConverter: ResultURIConverter
    // replay: ReplayDispatcher
    // parser: RequestParser
    // query: QueryRenderer
    // exception: ExceptionRenderer
    // collection: WaybackCollection
    // - resourceStore: ResourceStore
    // - resourceIndex: ResourceIndex
    // configs: Properties
    // liveWebRedirector: LiveWebRedirector
    WaybackCollection collection;
    ResourceStore resourceStore;
    ResourceIndex resourceIndex;
   
    HttpServletRequest httpRequest;
    HttpServletResponse httpResponse;
    RequestDispatcher requestDispatcher;
   
    RequestParser parser;
    //ResultURIConverter uriConverter;
    QueryRenderer query;
    ReplayDispatcher replay;
   
    WaybackRequest wbRequest;
   
    ReplayRenderer replayRenderer;
   
    /**
     * setup HttpServletRequest stubs
     * @param contextPath
     */
    protected void setupRequestStub(String contextPath, String uri, String contextPathPrefix) {
        EasyMock.expect(httpRequest.getRequestURI()).andStubReturn(uri);
        EasyMock.expect(httpRequest.getRequestURL()).andStubReturn(new StringBuffer(uri));
        //EasyMock.expect(httpRequest.getQueryString()).andReturn(null);
        // remote address test
        //EasyMock.expect(httpRequest.getHeader("X-Forwarded-For")).andReturn(null);
        // Ajax mode test
        //EasyMock.expect(httpRequest.getHeader("X-Requested-With")).andReturn("XMLHttpRequest");

        // used by RequestMapper#getRequestPathPrefix(HttpServletRequest)
        // typical value found in ia-wayback-projects/projects/global-wayback/configs/local/wayback.properties
        // TODO: RequestMapper#getRequestContextPath(HttpServletRequest) assumes value of this
        // attribute ends with "/". RequestMapper has constant declaration for
        // "webapp-request-context-path-prefix", but it's private.
        EasyMock.expect(
                httpRequest.getAttribute("webapp-request-context-path-prefix"))
                .andStubReturn(contextPathPrefix);
       
        EasyMock.expect(httpRequest.getLocalName()).andStubReturn("localhost");
        // commented out because these are default behavior for stub ("Nice") mock.
        //EasyMock.expect(httpRequest.getAuthType()).andReturn(null).anyTimes();
        //EasyMock.expect(httpRequest.getRemoteUser()).andReturn(null).anyTimes();
        //EasyMock.expect(httpRequest.getHeader(WaybackRequest.REQUEST_AUTHORIZATION)).andReturn(null);
        EasyMock.expect(httpRequest.getLocalPort()).andStubReturn(8080);
        EasyMock.expect(httpRequest.getContextPath()).andStubReturn("/static");
        EasyMock.expect(httpRequest.getLocale()).andStubReturn(Locale.CANADA_FRENCH);
        EasyMock.expect(httpRequest.getRequestDispatcher(EasyMock.<String>notNull())).andStubReturn(requestDispatcher);
        //EasyMock.expect(httpRequest.getCookies()).andReturn(null).anyTimes();
    }
   
    // values used in global wayback configuration.
    public static final String WEB_PREFIX = "/web/";
    public static final String STATIC_PREFIX = "/static/";
   
    /* (non-Javadoc)
     * @see junit.framework.TestCase#setUp()
     */
    protected void setUp() throws Exception {
        super.setUp();
        cut = new AccessPoint();
        cut.setEnablePerfStatsHeader(false);
        cut.setEnableMemento(false);
        cut.setExclusionFactory(null);
        cut.setExactSchemeMatch(false); // default
        cut.setExactHostMatch(false); // default
        cut.setEnableWarcFileHeader(false);
        cut.setReplayPrefix(WEB_PREFIX);
        cut.setQueryPrefix(WEB_PREFIX);
        cut.setStaticPrefix(STATIC_PREFIX);
       
        resourceStore = EasyMock.createMock(ResourceStore.class);
        resourceIndex = EasyMock.createMock(ResourceIndex.class);
        collection = new WaybackCollection();
        collection.setResourceIndex(resourceIndex);
        collection.setResourceStore(resourceStore);
        cut.setCollection(collection);
       
        // behavior returning null are commented out because EasyMock provides them by default.
        httpRequest = EasyMock.createNiceMock(HttpServletRequest.class);
        httpResponse = EasyMock.createNiceMock(HttpServletResponse.class);
        // RequestDispatcher - setup expectations, call replay() and verify() if
        // method calls are expected.
        requestDispatcher = EasyMock.createMock(RequestDispatcher.class);
        // Memento mode - only called when enableMemento==true.
        //EasyMock.expect(httpRequest.getHeader(MementoUtils.ACCEPT_DATETIME)).andReturn(null);
        setupRequestStub("/", "/", null);
       
        // as we mock-ify RequestParser, WaybackRequest can be independent of httpRequest.
        // it suggests HttpServletRequest method calls above are better be made through
        // RequestParser (TODO)
        wbRequest = new WaybackRequest();
        parser = EasyMock.createMock(RequestParser.class);
        cut.setParser(parser);
        EasyMock.expect(parser.parse(httpRequest, cut)).andReturn(wbRequest);
        EasyMock.replay(parser);
       
        query = EasyMock.createMock(QueryRenderer.class);
        cut.setQuery(query);
       
        replay = EasyMock.createMock(ReplayDispatcher.class);
        cut.setReplay(replay);
       
        replayRenderer = EasyMock.createMock(ReplayRenderer.class);
       
        {
            ArchivalUrlResultURIConverter uc = new ArchivalUrlResultURIConverter();
            uc.setReplayURIPrefix("/web/");
            cut.setUriConverter(uc);
        }
    }
   
    public static Resource createTestHtmlResource(String uri, String timestamp, byte[] payloadBytes) throws IOException {
        TestWARCRecordInfo recinfo = TestWARCRecordInfo.createCompressedHttpResponse("text/html", payloadBytes);
        recinfo.setCreate14DigitDateFromDT14(timestamp);
        if (uri != null) recinfo.setUrl(uri);
        TestWARCReader ar = new TestWARCReader(recinfo);
        WARCRecord rec = ar.get(0);
        WarcResource resource = new WarcResource(rec, ar);
        resource.parseHeaders();
        return resource;
    }
    public static Resource createTestHtmlResource(String timestamp, byte[] payloadBytes) throws IOException {
        // by passing null to uri, default "http://test.example.com/" will be used.
        return createTestHtmlResource(null, timestamp, payloadBytes);
    }
    public static Resource createTestRevisitResource(String timestamp, int len, boolean withHeader) throws IOException {
        TestWARCRecordInfo recinfo = TestWARCRecordInfo.createRevisitHttpResponse("text/html", len, withHeader);
        recinfo.setCreate14DigitDateFromDT14(timestamp);
        TestWARCReader ar = new TestWARCReader(recinfo);
        WARCRecord rec = ar.get(0);
        WarcResource resource = new WarcResource(rec, ar);
        resource.parseHeaders();
        return resource;
    }
   
    /**
     * checks if {@code ts} has expected format (YYYYmmddHHMMSS)
     * @param ts timestamp string to check
     * @return true if ok, false otherwise
     */
    protected static boolean validTimestamp(String ts) {
        return ts != null && Pattern.matches("\\d{14}", ts);
    }
    /**
     * Transform input date to 14-digit timestamp:
     * 2007-08-29T18:00:26Z => 20070829180026
     * (stolen from WARCRecordToSearchResultAdapter - move that method to ArchiveUtils!)
     * @param input date text in ISOZ format.
     * @return date text in DT14 format.
     */
    private static String transformWARCDate(final String input) {
        StringBuilder output = new StringBuilder(14);
        output.append(input.substring(0,4));
        output.append(input.substring(5,7));
        output.append(input.substring(8,10));
        output.append(input.substring(11,13));
        output.append(input.substring(14,16));
        output.append(input.substring(17,19));
        return output.toString();
    }
   
    /**
     * given a sequence of {@link WarcResource}s,
     * <ul>
     * <li>build CaptureSearchResults, filled with CaptureSearchResult instances, which
     * have auto-generated unique filename and offset of 0. this is necessary for equality
     * (see {@link CaptureSearchResultMatcher}).</li>
     * <li>setup ResourceStore mock to return Resource for each CaptureSearchResult.<li>
     * <li>setup ResourceIndex mock to return CaptureSearchResults.</li>
     * <li>if {@code closestIndex} {@code >= 0}, set corresponding CaptureSearchResult's
     * closest flag, and also set up ReplsyDispatcher.getClosest() mock to return it.</li>
     * </ul>
     * @param closestIndex 0-based index of resource to be marked as <i>closest</i>
     * @param resources sequence of WarcResources
     * @return CaptureSearchResults built
     */
    protected CaptureSearchResults setupCaptures(int closestIndex, Resource... resources) throws Exception {
        CaptureSearchResults results = new CaptureSearchResults();
        for (Resource res : resources) {
            CaptureSearchResult result = new CaptureSearchResult();
            // TODO: Resource should have methods for accessing URI and date
            if (res instanceof WarcResource) {
                // TODO: want to use WARCRecordToSearchResultAdapter? WarcResource
                // has no method to retrieve underlining WARCRecord.
                ArchiveRecordHeader h = ((WarcResource)res).getWarcHeaders();
                String originalUrl = h.getUrl();
                String ts = (String)h.getHeaderValue("WARC-Date");
                // WARC-Date is in ISOZ format.
                ts = transformWARCDate(ts);
                result.setOriginalUrl(originalUrl);
                result.setCaptureTimestamp(ts);
                result.setOffset(0);
                // this is (W)ARC file name in real practice. here we use
                // DT14 timestamp as pseudo filename (.warc.gz suffix is not
                // essential).
                result.setFile(ts + ".warc.gz");
            } else if (res instanceof ArcResource) {
                // TODO: should use ARCRecordToSearchResultAdapter? ArcResource has
                // getArcRecord() methods whose result may be cast to ARCRecord.
                // NB: ArcResource#getARCMetadata() creates a new Map object.
                Map<String, String> meta = ((ArcResource)res).getARCMetadata();
                String originalUrl = meta.get(ArchiveFileConstants.URL_FIELD_KEY);
                String ts = meta.get(ArchiveFileConstants.DATE_FIELD_KEY);
                result.setOriginalUrl(originalUrl);
                result.setCaptureTimestamp(ts);
            } else {
                throw new AssertionError("unexpected Resource type: " + res.getClass());
            }
            result.setHttpCode(Integer.toString(res.getStatusCode()));
            // CaptureSearchResultMatcher fails without this, but actual value does not
            // matter. so set it to 0.
            result.setOffset(0);
            assertTrue("invalid timestamp " + result.getCaptureTimestamp(),
                    validTimestamp(result.getCaptureTimestamp()));
            if (closestIndex == 0) {
                result.setClosest(true);
                results.setClosest(result);
                EasyMock.expect(replay.getClosest(wbRequest, results)).andReturn(result);
            }
           
            // Note AccessPoint passes a copy of CaptureSearchResult in some case (ex. Replay_Revisit() test).
            // so we need to use custom argument matcher.
            EasyMock.expect(resourceStore.retrieveResource(eqCaptureSearchResult(result))).andReturn(res).anyTimes();
           
            results.addSearchResult(result);
            --closestIndex;
        }
        EasyMock.expect(resourceIndex.query(wbRequest)).andReturn(results);
       
        return results;
    }
   
    // REFACTORING THOUGHTS: WaybackRequest.setReplayRequest() could take requestUrl and replayTimestamp
    // it is semantically more clear.
    public static void setReplayRequest(WaybackRequest wbRequest, String requestUrl, String replayTimestamp) {
        wbRequest.setReplayRequest();
        wbRequest.setRequestUrl(requestUrl);
        wbRequest.setReplayTimestamp(replayTimestamp);
    }

    /**
     * test basic behavior 1.
     * <ul>
     * <li>no authorization</li>
     * <li>no exclusion factory</li>
     * </ul>
     * and,
     * <ul>
     * <li>
     * <li>there's no capture on date matching the request.</li>
     * <li>closest capture is not a revisit</li>
     * </li>
     * this shall result in redirect (302) response to the URL with
     * closest capture date in date component.
     *
     * alternative path: {@link #testBounceToReplayPrefix()}
     * @throws Exception
     */
    public void testHandleRequest_Replay_1() throws Exception {
        // make sure wbRequesat.requestUrl, replayTimestamp are set up.
        setReplayRequest(wbRequest, "http://www.example.com/", "20100601123456");
       
//        // TODO: originalUrl can be different from wbRequst.requestUrl, and it will be
//        // reflected to redirect URL (worth testing).
//        CaptureSearchResults results = createCaptureSearchResults(
//                "20100601000000", "http://www.example.com/", "200");
//        CaptureSearchResult closest = results.getClosest();
//        // TODO: this can be different from wbRequst.requestUrl, and it will be reflected
//        // to redirect URL.
//        closest.setOriginalUrl("http://www.example.com/");
//        closest.setHttpCode("200");
//        // closest.captureTimestamp != wbRequest.replayTimestamp
//        closest.setCaptureTimestamp("20100601000000");
        // Resource below has originalUrl="http://test.example.com/", which is different from
        // wbRequest.requestUrl above. originalUrl shall be reflected to resultant redirect URL.
        CaptureSearchResults results = setupCaptures(
                0,
                createTestHtmlResource("20100601000000", "hogheogehoge\n".getBytes("UTF-8"))
                );
        // handleRequest()
        // calls handleReplay()
        // - calls checkInterstitialRedirect()
        // - calls selfRedirectCanonicalizer.urlStringToKey(requestURL) if non-null
        // - calls queryIndex(), which calls collection.resourceIndex.query(wbRequest)
        //     which in turn returns results above (setup in setupCaptures(...))
        // - calls replay.getClosest()
        // - calls checkAnchorWindow()
        // - calls getResource(closest, skipFiles), which
        //   - first checks if closest is in skipFiles (and throws
        //     ResourceNotAvailableException if it is),
        //   - then calls collection.resourceStore.retrieveResource(closest),
        //     which returns Resource above.
       
        // when closest's timestamp is different from replay requests's timestamp, it redirects
        // to closest's timestamp.
        httpResponse.setStatus(302);
        httpResponse.setHeader("Location", "/web/20100601000000/http://test.example.com/");
       
        EasyMock.replay(httpRequest, httpResponse, resourceIndex, resourceStore, replay);
       
        cut.init();
        boolean r = cut.handleRequest(httpRequest, httpResponse);
       
        EasyMock.verify(resourceIndex, resourceStore, replay);
       
        assertTrue("handleRequest return value", r);
    }
   
    /**
     * basic replay test part 2.
     * there's a capture whose capture date matches the request.
     * @throws Exception
     */
    public void testHandleRequest_Replay_2() throws Exception {
        // make sure wbRequesat.requestUrl, replayTimestamp are set up.
        setReplayRequest(wbRequest, "http://test.example.com/", "20100601000000");
       
        // there's capture with timestamp exactly requested for.
        Resource payloadResource = createTestHtmlResource("20100601000000", "hogheogehoge\n".getBytes("UTF-8"));
        CaptureSearchResults results = setupCaptures(
                0,
                payloadResource
                );
        CaptureSearchResult closest = results.getClosest();
       
        // when closest's timestamp == request's timestamp,
        // it gets ReplayRenderer with replay.getRenderer(wbRequest, closest, httpHeaderResource, payloadResource),
        // and calls renderResource() on it.
        EasyMock.expect(replay.getRenderer(wbRequest, closest, payloadResource, payloadResource)).andReturn(replayRenderer);
        // calls replayRenderer.renderResource(...)
        replayRenderer.renderResource(httpRequest, httpResponse, wbRequest,
                closest, payloadResource, payloadResource, cut.getUriConverter(),
                results);
       
        EasyMock.replay(httpRequest, httpResponse, resourceIndex, resourceStore, replay);
       
        cut.init();
        boolean r = cut.handleRequest(httpRequest, httpResponse);

        EasyMock.verify(resourceIndex, resourceStore, replay);
       
        assertTrue("handleRequest return value", r);
    }
   
    /**
     * test CaptureSearchResult equality by file and offset.
     */
    public static class CaptureSearchResultMatcher implements IArgumentMatcher {
        private CaptureSearchResult expected;
        public CaptureSearchResultMatcher(CaptureSearchResult expected) {
            this.expected = expected;
        }
        @Override
        public boolean matches(Object actual) {
            // CaptureSearchResult is compared by file name and offset. this is how
            // AccessPoint#retrievePayloadForIdenticalContentRevisit(...) retrieves previous capture.
            // TODO: this could be defined as CaptureSearchResult#equals(Object).
            if (!(actual instanceof CaptureSearchResult)) return false;
            String file = ((CaptureSearchResult)actual).getFile();
            long offset =  ((CaptureSearchResult)actual).getOffset();
            if (expected.getOffset() != offset) return false;
            return file == null ? expected.getFile() == null : file.equals(expected.getFile());
        }

        @Override
        public void appendTo(StringBuffer buffer) {
            buffer.append("eqCaptureSearchResult(");
            buffer.append(expected.getFile());
            buffer.append(",");
            buffer.append(expected.getOffset());
            buffer.append(")");
        }
       
    }
    public static CaptureSearchResult eqCaptureSearchResult(CaptureSearchResult expected) {
        EasyMock.reportMatcher(new CaptureSearchResultMatcher(expected));
        return null;
    }
   
    /**
     * test of revisit.
     * closest capture is a revisit.
     * @throws Exception
     */
    public void testHandleRequest_Replay_Revisit() throws Exception {
        setReplayRequest(wbRequest, "http://www.example.com/", "20100601000000");
        // closest SearchResult has isDuplicateDigest() == true.
        byte[] payload = "hogehogehogehoge\n".getBytes("UTF-8");
        Resource payloadResource = createTestHtmlResource("20100501000001", payload);
        Resource headerResource = createTestRevisitResource("20100601000000", payload.length, true);
        CaptureSearchResults results = setupCaptures(1, payloadResource, headerResource);

        CaptureSearchResult previous = results.getResults().get(0);
        CaptureSearchResult closest = results.getClosest();
//        previous.setFile("aaa.warc.gz");
//        previous.setOffset(0);
        closest.flagDuplicateDigest(previous); // right? TODO: could be done in setupCaptures()
        assertTrue(closest.isDuplicateDigest());
        assertTrue(closest.getDuplicatePayloadFile() != null);
        assertTrue(closest.getDuplicatePayloadOffset() != null);

        EasyMock.expect(replay.getRenderer(wbRequest, closest, headerResource, payloadResource)).andReturn(replayRenderer);
        // calls replayRenderer.renderResource(...)
        replayRenderer.renderResource(httpRequest, httpResponse, wbRequest,
                closest, headerResource, payloadResource, cut.getUriConverter(),
                results);
       
        EasyMock.replay(httpRequest, httpResponse, resourceIndex, resourceStore, replay);
       
        cut.init();
        boolean r = cut.handleRequest(httpRequest, httpResponse);

        EasyMock.verify(resourceIndex, resourceStore, replay);
       
        assertTrue("handleRequest return value", r);

        // TODO: failure case: closest.duplicatePayloadFile != null -> ResourceNotAvailableException
        // TODO: failure case: self-redirecting -> calls finxNextClosest() and fails if there's no more closest.
        // wbRequest.timestampSearchKey == true -> calls queryIndex() once again.
       
    }
   
    /**
     * old-style WARC revisit (no HTTP status line and header,
     * Content-Length in WARC header is zero).
     * it shall replay HTTP status line, headers and content from previous matching capture.
     * @throws Exception
     */
    public void testHandleRequest_Replay_OldWARCRevisit() throws Exception {
        // TODO - it'd be better to define an interface in Resource class so that AccessPoint
        // needs not to have separate execution path for this. that's easier to test.
    }
   
    /**
     * old-style ARC revisit (no mimetype, filename is '-')
     * @throws Exception
     */
    public void testHandleRequest_Replay_OldARCRevisit() throws Exception {
        // ditto - see TODO comment above.
    }
   
    public static final Resource createTest502Resource() throws IOException {
        byte[] failPayload = "failed\n".getBytes("UTF-8");
        byte[] content = TestWARCRecordInfo.buildHttpResponseBlock("502 Bad Gateway", "text/plain", failPayload);
 
        TestWARCRecordInfo recinfo = new TestWARCRecordInfo(content);
        TestWARCReader ar = new TestWARCReader(recinfo);
        WARCRecord rec = ar.get(0);
        WarcResource resource = new WarcResource(rec, ar);
        resource.parseHeaders();
        return resource;
    }
   
    /**
     * if closest is not HTTP-success AND replaying embedded context (CSS, JavaScript, images, etc.),
     * use next closest with successful response, or for lower priority, a redirect, instead.
     * unless such capture is of the same timestamp as the replay request, redirect to the capture found.
     * @throws Exception
     */
    public void testHandleRequest_Replay_Embedded() throws Exception {
        // request timestamp is different from 'previous' below. it makes handleRequest
        // return redirect. in this case, Resource for 'previous' will not be retrieved.
        setReplayRequest(wbRequest, "http://test.example.com/style.css", "20100601000000");
        // if closest is not HTTP-success,
        // to have isAnyEmbeddedContext() return true - any of cSSContext, iMGContext, jSContext
        // frameWrapperContext, iFrameWrapperContext, objectEmbedContext has the same effect.
        wbRequest.setCSSContext(true);
        assertTrue(wbRequest.isAnyEmbeddedContext());
       
        CaptureSearchResults results = setupCaptures(
                1,
                createTestHtmlResource("http://test.example.com/style.css",
                        "20100501000000", "hogheogehoge\n".getBytes("UTF-8")),
                createTest502Resource()
                );
        CaptureSearchResult closest = results.getClosest();
        assertTrue(closest.isHttpError());
       
        // or wbRequest.setBestLatestReplayRequest();
        final String expectedRedirectURI = "/web/20100501000000cs_/http://test.example.com/style.css";
        httpResponse.setHeader("Location", expectedRedirectURI);
        httpResponse.setStatus(302);
        // TODO: extraHeaders expectations?
       
        EasyMock.replay(httpRequest, httpResponse, resourceIndex, resourceStore, query, replay);
       
        cut.init();
        boolean r = cut.handleRequest(httpRequest, httpResponse);
        // handleReplay throws BetterRequestException and handled inside handleRequest()
        // exception will not be thrown out of handleRequest().
       
        EasyMock.verify(httpResponse, resourceIndex, resourceStore, query, replay);
        assertTrue("handleRequest return value", r);
    }
   
    // REFACTORING THOUGHTS: WaybackRequet.setUrlCaptureQueryRequest() could take requestUrl and replayTimestamp.
    public static final void setCaptureQueryRequest(WaybackRequest wbRequest, String requestUrl, String replayTimestamp) {
        wbRequest.setCaptureQueryRequest();
        wbRequest.setRequestUrl(requestUrl);
        wbRequest.setReplayTimestamp(replayTimestamp);
    }
   
    // REFACTORING THOUGHTS: query rendering could be done in the same mechanism as replay rendering.
    // there's no particular reason CaptureSearchResults rendering and UrlSearchResults
    // rendering must be implemented in the same class. they share nothing.
    // ReplayRenderer and QueryRenderer may be unified by passing UIResults instead of
    // (CaptureSearchResult, Resource, CaptureSearchResults) for ReplayRenderer, and
    // (CaptureSearchResults / UrlSearchResults) for QueryRenderer.
    // this way, query.Renderer could be replaced by generic "variant dispatcher" class
    // that dispatches rendering to different JSPs depending on the type of output (HTML
    // or XML).
   
    public void testHandleRequest_CaptureSearchResults() throws Exception {
        setCaptureQueryRequest(wbRequest, "http://www.example.com/", "20100601123456");

        // handleRequest()
        // redirect to queryPrefix + translateRequestPathQuery(httpRequest)
        //   if bounceToQueryPrefix is true (not tested here)
        // copies exactHostMatch to wbRequest.exactHost (TODO: should be done by parser?)
        // calls handleQuery()
        // - calls queryIndex(), which calls collection.resourceIndex.query(),
        //     which returns CaptureSearchResults
        //   (unexpected object from queryIndex() results in WaybackException("Unknown index format").
        //    this is considered to be a programming/configuration error. not tested.)
        CaptureSearchResults results = new CaptureSearchResults();
        CaptureSearchResult result = new CaptureSearchResult();
        results.setClosest(result);
        EasyMock.expect(resourceIndex.query(wbRequest)).andReturn(results);
        // - calls MementoUtils.printTimemapResponse(results, wbRequest, httpResponse) instead
        //     if wbRequst.isMementoTimemapRequest() (N/A here) (TODO: can we move this to
        //     QueryRenderer implementation?)
        // - calls query.renderCaptureResults(...)
        query.renderCaptureResults(httpRequest, httpResponse, wbRequest, results, cut.getUriConverter());
       
        EasyMock.replay(httpRequest, httpResponse, resourceIndex, query);
       
        cut.init();
        boolean r = cut.handleRequest(httpRequest, httpResponse);
       
        EasyMock.verify(query);
       
        // result shall have closest flag set (FIrefox proxy plugin expects this)
        assertTrue("closest flag", result.isClosest());
       
        assertTrue("handleRequest return value", r);
       
    }
   
    // REFACTORING THOUGHTS: WaybackRequet.setUrlQueryRequest() could take requestUrl and replayTimestamp.
    public static void setUrlQueryRequest(WaybackRequest wbRequest, String requestUrl, String replayTimestamp) {
        wbRequest.setUrlQueryRequest();
        wbRequest.setRequestUrl(requestUrl);
        wbRequest.setReplayTimestamp(replayTimestamp);
    }
   
    public void testHandleRequest_UrlSearchResults() throws Exception {
        setUrlQueryRequest(wbRequest, "http://www.example.com/", "20100601123456");

        // AccessPoint is not concerned of the details of UrlSearchResults. it just
        // forwards the request to QueryRenderer. so we leave it uninitialized here.
        UrlSearchResults results = new UrlSearchResults();
        EasyMock.expect(resourceIndex.query(wbRequest)).andReturn(results);
       
        // EXPECTATION: AccessPoint.handleQuery() calls query.renderUrlResults().
        query.renderUrlResults(httpRequest, httpResponse, wbRequest, results, cut.getUriConverter());
       
        EasyMock.replay(httpRequest, httpResponse, query, resourceIndex);
       
        cut.init();
        boolean r = cut.handleRequest(httpRequest, httpResponse);
       
        EasyMock.verify(query, resourceIndex);
        assertTrue("handleRequest return value", r);
    }

    /**
     * if bounceToReplayPrefix is true, replay request is redirected to
     * other access point.
     * @throws Exception
     */
    public void testBounceToReplayPrefix() throws Exception {
        final String URL = "http://www.example.com/";
        final String TIMESTAMP = "20100601123456";
       
        wbRequest.setReplayRequest();
        wbRequest.setRequestUrl(URL);
        wbRequest.setReplayTimestamp(TIMESTAMP);

        EasyMock.reset(httpRequest);
        EasyMock.expect(httpRequest.getRequestURI()).andStubReturn("/" + TIMESTAMP + "/" + URL);
        EasyMock.expect(httpRequest.getLocalName()).andStubReturn("localhost");
        EasyMock.expect(httpRequest.getLocalPort()).andStubReturn(8080);
        EasyMock.expect(httpRequest.getContextPath()).andStubReturn("/");
        EasyMock.expect(httpRequest.getLocale()).andStubReturn(Locale.CANADA_FRENCH);

        final String replayPrefix = "http://test.archive.org/";
        cut.setBounceToReplayPrefix(true);
        cut.setReplayPrefix(replayPrefix);

        final String suffix = "/" + TIMESTAMP + "/" + URL;
        httpResponse.sendRedirect(replayPrefix + suffix);
       
        EasyMock.replay(httpRequest, httpResponse);
       
        cut.handleRequest(httpRequest, httpResponse);

    }
   
    // TODO: the way AccessPoint is reused for rendering static resource looks inefficient.
    // bounceToReplayPrefix and bounceToQueryPrefix are always configured in pair, and they
    // are set to true only for static resource AccessPoint.
   
    /**
     * static AccessPoint - configured with
     * <ul>
     * <li>accessPointPath=<code>${wayback.staticPrefix}</code></li>
     * <li>serveStatic=true</li>
     * <li>bounceToReplayPrefix=true</li>
     * <li>bounceToQueryPrefix=true</li>
     * </ul>
     * when {@link RequestParser#parse(HttpServletRequest, AccessPoint)} returns null,
     * request is forwarded to dispatchLocal() for rendering static resources.
     * @throws Exception
     */
    public void testDispatchLocal() throws Exception {
        // first reset the mock for overriding getAttribute(), getRequestURI(), and getRequestURL()
        EasyMock.reset(httpRequest);
        EasyMock.expect(httpRequest.getLocalName()).andStubReturn("localhost");
        EasyMock.expect(httpRequest.getLocalPort()).andStubReturn(8080);
        EasyMock.expect(httpRequest.getContextPath()).andStubReturn("/static");
        EasyMock.expect(httpRequest.getLocale()).andStubReturn(Locale.CANADA_FRENCH);
        EasyMock.expect(httpRequest.getRequestDispatcher(EasyMock.<String>notNull())).andStubReturn(requestDispatcher);

        // used by RequestMapper#getRequestPathPrefix(HttpServletRequest)
        // typical value found in ia-wayback-projects/projects/global-wayback/configs/local/wayback.properties
        // TODO: RequestMapper#getRequestContextPath(HttpServletRequest) assumes value of this
        // attribute ends with "/". RequestMapper has constant declaration for
        // "webapp-request-context-path-prefix", but it's private.
        EasyMock.expect(
                httpRequest.getAttribute("webapp-request-context-path-prefix"))
                .andStubReturn("/static/");
        // override getRequestURI() behavior
        EasyMock.expect(httpRequest.getRequestURI()).andStubReturn("/static/aaa.css");
        EasyMock.expect(httpRequest.getRequestURL()).andStubReturn(new StringBuffer("/static/aaa.css"));
       
        // reconfigure RequestParser to return null, which signifies that
        // there's no dynamic handler and the request shall be mapped to local static resource.
        // (AccessPoint#dispatchLocal(HttpServletRequest))
        EasyMock.reset(parser);
        EasyMock.expect(parser.parse(httpRequest, cut)).andReturn(null);
       
        // AccessPoint#dispatchLocal() checks existence of the file if ServletContext#getRealPath()
        // returns non-null value for translated request path. have it skip the test by returning
        // null. otherwise dispatchLocal() will fail.
        ServletContext servletContext = EasyMock.createMock(ServletContext.class);
        EasyMock.expect(servletContext.getRealPath(EasyMock.<String> notNull()))
                .andStubReturn(null);
        cut.setServletContext(servletContext);
       
        // Expectation: AccessPoint#dispatchLocal() eventually calls RequestDispatcher#forward(...)
        requestDispatcher.forward(httpRequest, httpResponse);

        EasyMock.replay(httpRequest, parser, servletContext, requestDispatcher);
       
        assertEquals("aaa.css", RequestMapper.getRequestContextPath(httpRequest));
       
        // AccessPoint#dispatchLocal() returns immediately if serveStatis is false.
        cut.setServeStatic(true);
        cut.init();
        boolean r = cut.handleRequest(httpRequest, httpResponse);
       
        EasyMock.verify(parser, requestDispatcher);
       
        assertTrue("handleRequest return value", r);
       
    }
   
    // *** Memento Tests ***
   
    // REFACTORING THOUGHTS: Memento annotations (adding response headers) could be implemented
    // as decorator of replay renderer. because of this possibility, I separated out tests for
    // memento headers here.

    public void testMemento_replay_exactCapture() throws Exception {
    // Setting default locale to something else than english to be sure date-formatting is working across locales.
    Locale.setDefault(Locale.FRANCE);
   
        final String AGGREGATION_PREFIX = "http://web.archive.org";
       
        cut.setEnableMemento(true);
        cut.setConfigs(new Properties());
        cut.getConfigs().setProperty(MementoUtils.AGGREGATION_PREFIX_CONFIG, AGGREGATION_PREFIX);
       
        // make sure wbRequesat.requestUrl, replayTimestamp are set up.
        setReplayRequest(wbRequest, "http://www.example.com/", "20100601000000");
        assertFalse(wbRequest.isMementoTimegate());
        Resource payloadResource = createTestHtmlResource("20100601000000", "hogehogehogehoge\n".getBytes("UTF-8"));
        CaptureSearchResults results = setupCaptures(
                0,
                payloadResource
                );
        CaptureSearchResult closest = results.getClosest();
       
        // when closest's timestamp == request's timestamp,
        // it gets ReplayRenderer with replay.getRenderer(wbRequest, closest, httpHeaderResource, payloadResource),
        // and calls renderResource() on it.
        EasyMock.expect(replay.getRenderer(wbRequest, closest, payloadResource, payloadResource)).andReturn(replayRenderer);
        // calls replayRenderer.renderResource(...)
        replayRenderer.renderResource(httpRequest, httpResponse, wbRequest,
                closest, payloadResource, payloadResource, cut.getUriConverter(),
                results);
       
        // key expectations of this test
        // called through MementoUtils.addMementoHeaders(...)
        final String expectedMementoDateTime = "Tue, 01 Jun 2010 00:00:00 GMT";
        httpResponse.setHeader(MementoUtils.MEMENTO_DATETIME, expectedMementoDateTime);
        // MementoUtils.generateMementoLinkHeaders(...)
        // TODO: actually it is acceptable to have various rels in different order.
        final String expectedMementoLink = String.format(
                "<%1$s>; rel=\"original\", " +
                "<%2$s%3$stimemap/link/%1$s>; rel=\"timemap\"; type=\"application/link-format\", " +
                "<%2$s%3$s%1$s>; rel=\"timegate\", " +
                "<%2$s%3$s%4$s/%1$s>; rel=\"first last memento\"; datetime=\"%5$s\"",
                "http://www.example.com/", AGGREGATION_PREFIX, WEB_PREFIX, "20100601000000", expectedMementoDateTime);
        httpResponse.setHeader(MementoUtils.LINK, expectedMementoLink);
       
        EasyMock.replay(httpRequest, httpResponse, resourceIndex, resourceStore, replay);
       
        cut.init();
        boolean r = cut.handleRequest(httpRequest, httpResponse);

        EasyMock.verify(resourceIndex, resourceStore, replay);
       
        assertTrue("handleRequest return value", r);
    }

    public void testMemento_replay_nearbyCapture() throws Exception {
        cut.setEnableMemento(true);
        // make sure wbRequesat.requestUrl, replayTimestamp are set up.
        setReplayRequest(wbRequest, "http://www.example.com/", "20100601123456");
        assertFalse(wbRequest.isMementoTimegate());
       
        Resource payloadResource = createTestHtmlResource("20100601000000", "hogehogehogehoge\n".getBytes("UTF-8"));
        CaptureSearchResults results = setupCaptures(0, payloadResource);
        // handleRequest()
        // calls handleReplay()
        // - calls checkInterstitialRedirect()
        // - calls selfRedirectCanonicalizer.urlStringToKey(requestURL) if non-null
        // - calls queryIndex(), which calls collection.resourceIndex.query(wbRequest)
       
        // redirects to URL for closest capture.
        // also has Link header.
        httpResponse.setHeader("Link", String.format("<%s>; rel=\"original\"", "http://test.example.com/"));
        httpResponse.setStatus(302);
        httpResponse.setHeader("Location", "/web/20100601000000/http://test.example.com/");
       
        EasyMock.replay(httpRequest, httpResponse, resourceIndex, resourceStore, replay);
       
        cut.init();
        boolean r = cut.handleRequest(httpRequest, httpResponse);
       
        EasyMock.verify(httpResponse, resourceIndex, resourceStore, replay);
       
        assertTrue("handleRequest return value", r);
    }

    /**
     * Timemap == Memento rendering of capture query (CaptureSearchResult).
     */
    public void testMementoTimemap() throws Exception {
        cut.setEnableMemento(true);
        setCaptureQueryRequest(wbRequest, "http://www.example.com/", "20100601000000");
        wbRequest.setMementoTimegate();
       
        // handleRequest()
        // redirect to queryPrefix + translateRequestPathQuery(httpRequest)
        //   if bounceToQueryPrefix is true (not tested here)
        // copies exactHostMatch to wbRequest.exactHost (TODO: should be done by parser?)
        // calls handleQuery()
        // - calls queryIndex(), which calls collection.resourceIndex.query(),
        //     which returns CaptureSearchResults
        //   (unexpected object from queryIndex() results in WaybackException("Unknown index format").
        //    this is considered to be a programming/configuration error. not tested.)
        CaptureSearchResults results = new CaptureSearchResults();
        CaptureSearchResult result = new CaptureSearchResult();
        results.setClosest(result);
        EasyMock.expect(resourceIndex.query(wbRequest)).andReturn(results);
        // - calls MementoUtils.printTimemapResponse(results, wbRequest, httpResponse) instead
        //     if wbRequst.isMementoTimemapRequest() (N/A here) (TODO: can we move this to
        //     QueryRenderer implementation?)
        // - calls query.renderCaptureResults(...)
        query.renderCaptureResults(httpRequest, httpResponse, wbRequest, results, cut.getUriConverter());
       
        EasyMock.replay(httpRequest, httpResponse, resourceIndex, query);
       
        cut.init();
        boolean r = cut.handleRequest(httpRequest, httpResponse);
       
        EasyMock.verify(query);
       
        // result shall have closest flag set (FIrefox proxy plugin expects this)
        assertTrue("closest flag", result.isClosest());
       
        assertTrue("handleRequest return value", r);
       
    }
   
    /**
     * test for local static resource when enableMemento=true.
     * expectations:
     * <ul>
     * <li>do-not-negotiate Link header is set.</ul>
     * </ul>
     * @throws Exception
     */
    public void testMemento_dispatchLocal() throws Exception {
        cut.setEnableMemento(true);
        cut.setServeStatic(true);

        // first reset the mock for overriding getAttribute(), getRequestURI(), and getRequestURL()
        EasyMock.reset(httpRequest);
        setupRequestStub("/static", "/static/aaa.css", "/static/");

        EasyMock.expect(
                httpRequest.getAttribute("webapp-request-context-path-prefix"))
                .andStubReturn("/static/");
       
        // reconfigure RequestParser to return null, which signifies that
        // there's no dynamic handler and the request shall be mapped to local static resource.
        // (AccessPoint#dispatchLocal(HttpServletRequest))
        EasyMock.reset(parser);
        EasyMock.expect(parser.parse(httpRequest, cut)).andReturn(null);
       
        // AccessPoint#dispatchLocal() checks existence of the file if ServletContext#getRealPath()
        // returns non-null value for translated request path. have it skip the test by returning
        // null. otherwise dispatchLocal() will fail.
        ServletContext servletContext = EasyMock.createMock(ServletContext.class);
        EasyMock.expect(servletContext.getRealPath(EasyMock.<String> notNull()))
                .andStubReturn(null);
        cut.setServletContext(servletContext);
       
        // Expectation: AccessPoint#dispatchLocal() eventually calls RequestDispatcher#forward(...)
        requestDispatcher.forward(httpRequest, httpResponse);
       
        // key expectation in this test
        httpResponse.setHeader(MementoUtils.LINK, "<http://mementoweb.org/terms/donotnegotiate>; rel=\"type\"");

        EasyMock.replay(httpRequest, parser, servletContext, requestDispatcher);
       
        cut.init();
       
        boolean r = cut.handleRequest(httpRequest, httpResponse);
       
        EasyMock.verify(parser, requestDispatcher);

        assertTrue("handleRequest return value", r);
       
    }
   
}
TOP

Related Classes of org.archive.wayback.webapp.AccessPointTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.