org.trellisldp.http.AbstractLdpResourceTest.java Source code

Java tutorial

Introduction

Here is the source code for org.trellisldp.http.AbstractLdpResourceTest.java

Source

/*
 * 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.trellisldp.http;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.time.Instant.MAX;
import static java.time.Instant.ofEpochSecond;
import static java.time.ZoneOffset.UTC;
import static java.time.ZonedDateTime.parse;
import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static java.util.Date.from;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static java.util.stream.Collectors.toList;
import static javax.ws.rs.client.Entity.entity;
import static javax.ws.rs.core.HttpHeaders.CACHE_CONTROL;
import static javax.ws.rs.core.HttpHeaders.LINK;
import static javax.ws.rs.core.HttpHeaders.VARY;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static javax.ws.rs.core.Response.Status.CONFLICT;
import static javax.ws.rs.core.Response.Status.CREATED;
import static javax.ws.rs.core.Response.Status.GONE;
import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
import static javax.ws.rs.core.Response.Status.METHOD_NOT_ALLOWED;
import static javax.ws.rs.core.Response.Status.NO_CONTENT;
import static javax.ws.rs.core.Response.Status.NOT_ACCEPTABLE;
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
import static javax.ws.rs.core.Response.Status.OK;
import static javax.ws.rs.core.Response.Status.PRECONDITION_FAILED;
import static javax.ws.rs.core.Response.Status.UNSUPPORTED_MEDIA_TYPE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.AdditionalAnswers.returnsFirstArg;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
import static org.trellisldp.http.domain.HttpConstants.ACCEPT_DATETIME;
import static org.trellisldp.http.domain.HttpConstants.ACCEPT_PATCH;
import static org.trellisldp.http.domain.HttpConstants.ACCEPT_POST;
import static org.trellisldp.http.domain.HttpConstants.ACCEPT_RANGES;
import static org.trellisldp.http.domain.HttpConstants.APPLICATION_LINK_FORMAT;
import static org.trellisldp.http.domain.HttpConstants.DIGEST;
import static org.trellisldp.http.domain.HttpConstants.LINK_TEMPLATE;
import static org.trellisldp.http.domain.HttpConstants.MEMENTO_DATETIME;
import static org.trellisldp.http.domain.HttpConstants.PREFER;
import static org.trellisldp.http.domain.HttpConstants.RANGE;
import static org.trellisldp.http.domain.HttpConstants.UPLOADS;
import static org.trellisldp.http.domain.HttpConstants.UPLOAD_PREFIX;
import static org.trellisldp.http.domain.HttpConstants.WANT_DIGEST;
import static org.trellisldp.http.domain.RdfMediaType.APPLICATION_LD_JSON;
import static org.trellisldp.http.domain.RdfMediaType.APPLICATION_LD_JSON_TYPE;
import static org.trellisldp.http.domain.RdfMediaType.APPLICATION_N_TRIPLES;
import static org.trellisldp.http.domain.RdfMediaType.TEXT_TURTLE_TYPE;
import static org.trellisldp.http.domain.RdfMediaType.APPLICATION_SPARQL_UPDATE;
import static org.trellisldp.api.RDFUtils.TRELLIS_BNODE_PREFIX;
import static org.trellisldp.api.RDFUtils.TRELLIS_PREFIX;
import static org.trellisldp.api.RDFUtils.getInstance;
import static org.trellisldp.vocabulary.RDF.type;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.time.Instant;
import java.util.AbstractMap.SimpleEntry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;

import javax.ws.rs.core.Link;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.commons.io.IOUtils;
import org.apache.commons.rdf.api.BlankNode;
import org.apache.commons.rdf.api.Dataset;
import org.apache.commons.rdf.api.IRI;
import org.apache.commons.rdf.api.Literal;
import org.apache.commons.rdf.api.RDF;
import org.apache.commons.rdf.api.RDFTerm;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import org.mockito.Mock;

import org.trellisldp.api.AccessControlService;
import org.trellisldp.api.AgentService;
import org.trellisldp.api.Binary;
import org.trellisldp.api.BinaryService;
import org.trellisldp.api.IOService;
import org.trellisldp.api.Resource;
import org.trellisldp.api.ResourceService;
import org.trellisldp.api.Session;
import org.trellisldp.api.VersionRange;
import org.trellisldp.http.impl.HttpSession;
import org.trellisldp.io.JenaIOService;
import org.trellisldp.vocabulary.ACL;
import org.trellisldp.vocabulary.DC;
import org.trellisldp.vocabulary.LDP;
import org.trellisldp.vocabulary.Memento;
import org.trellisldp.vocabulary.Trellis;
import org.trellisldp.vocabulary.XSD;

/**
 * @author acoburn
 */
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@RunWith(JUnitPlatform.class)
abstract class AbstractLdpResourceTest extends JerseyTest {

    protected final static IOService ioService = new JenaIOService(null);

    private final static int timestamp = 1496262729;

    private final static Instant time = ofEpochSecond(timestamp);

    private final static ObjectMapper MAPPER = new ObjectMapper();

    private final static RDF rdf = getInstance();

    private final static IRI agent = rdf.createIRI("user:agent");

    private final static String UPLOAD_SESSION_ID = "upload-session-id";

    private final static BlankNode bnode = rdf.createBlankNode();

    private final static String BINARY_MIME_TYPE = "text/plain";

    private final static Long BINARY_SIZE = 100L;

    private final static String REPO1 = "repo1";
    private final static String REPO2 = "repo2";
    private final static String REPO3 = "repo3";
    private final static String REPO4 = "repo4";

    private final static String BASE_URL = "http://example.org/";

    private final static String RANDOM_VALUE = "randomValue";

    private final static String RESOURCE_PATH = REPO1 + "/resource";
    private final static String CHILD_PATH = RESOURCE_PATH + "/child";
    private final static String BINARY_PATH = REPO1 + "/binary";
    private final static String NON_EXISTENT_PATH = REPO1 + "/nonexistent";
    private final static String DELETED_PATH = REPO1 + "/deleted";
    private final static String USER_DELETED_PATH = REPO1 + "/userdeleted";

    private final static IRI identifier = rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH);
    private final static IRI root = rdf.createIRI(TRELLIS_PREFIX + REPO1);
    private final static IRI binaryIdentifier = rdf.createIRI(TRELLIS_PREFIX + BINARY_PATH);
    private final static IRI binaryInternalIdentifier = rdf.createIRI("file:some/file");
    private final static IRI nonexistentIdentifier = rdf.createIRI(TRELLIS_PREFIX + NON_EXISTENT_PATH);
    private final static IRI childIdentifier = rdf.createIRI(TRELLIS_PREFIX + CHILD_PATH);
    private final static IRI deletedIdentifier = rdf.createIRI(TRELLIS_PREFIX + DELETED_PATH);
    private final static IRI userDeletedIdentifier = rdf.createIRI(TRELLIS_PREFIX + USER_DELETED_PATH);

    protected final static Map<String, String> partitions = new HashMap<String, String>() {
        {
            put(REPO1, BASE_URL);
            put(REPO2, BASE_URL);
            put(REPO3, BASE_URL);
            put(REPO4, BASE_URL);
        }
    };

    protected final static Set<IRI> allModes = new HashSet<>();

    static {
        allModes.add(ACL.Append);
        allModes.add(ACL.Control);
        allModes.add(ACL.Read);
        allModes.add(ACL.Write);
    }

    @Mock
    protected ResourceService mockResourceService;

    @Mock
    protected BinaryService mockBinaryService;

    @Mock
    protected BinaryService.Resolver mockBinaryResolver;

    @Mock
    protected AccessControlService mockAccessControlService;

    @Mock
    protected AgentService mockAgentService;

    @Mock
    private Resource mockResource, mockVersionedResource, mockBinaryResource, mockDeletedResource,
            mockUserDeletedResource, mockBinaryVersionedResource;

    @Mock
    private Binary mockBinary;

    @Mock
    private InputStream mockInputStream;

    @BeforeAll
    public void before() throws Exception {
        super.setUp();
    }

    @AfterAll
    public void after() throws Exception {
        super.tearDown();
    }

    @BeforeEach
    public void setUpMocks() {
        when(mockResourceService.get(any(IRI.class), any(Instant.class))).thenReturn(of(mockVersionedResource));
        when(mockResourceService.get(eq(identifier))).thenReturn(of(mockResource));
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + "partition/resource"))))
                .thenReturn(of(mockResource));
        when(mockResourceService.get(eq(root))).thenReturn(of(mockResource));
        when(mockResourceService.get(eq(childIdentifier))).thenReturn(empty());
        when(mockResourceService.get(eq(childIdentifier), any(Instant.class))).thenReturn(empty());
        when(mockResourceService.get(eq(binaryIdentifier))).thenReturn(of(mockBinaryResource));
        when(mockResourceService.get(eq(binaryIdentifier), any(Instant.class)))
                .thenReturn(of(mockBinaryVersionedResource));
        when(mockResourceService.get(eq(nonexistentIdentifier))).thenReturn(empty());
        when(mockResourceService.get(eq(nonexistentIdentifier), any(Instant.class))).thenReturn(empty());
        when(mockResourceService.get(eq(deletedIdentifier))).thenReturn(of(mockDeletedResource));
        when(mockResourceService.get(eq(deletedIdentifier), any(Instant.class)))
                .thenReturn(of(mockDeletedResource));
        when(mockResourceService.getIdentifierSupplier()).thenReturn(() -> RANDOM_VALUE);

        when(mockResourceService.get(eq(userDeletedIdentifier))).thenReturn(of(mockUserDeletedResource));
        when(mockResourceService.get(eq(userDeletedIdentifier), any(Instant.class)))
                .thenReturn(of(mockUserDeletedResource));

        when(mockAgentService.asAgent(anyString())).thenReturn(agent);
        when(mockAccessControlService.getAccessModes(any(IRI.class), any(Session.class))).thenReturn(allModes);

        when(mockVersionedResource.getMementos()).thenReturn(
                asList(new VersionRange(ofEpochSecond(timestamp - 2000), ofEpochSecond(timestamp - 1000)),
                        new VersionRange(ofEpochSecond(timestamp - 1000), time),
                        new VersionRange(time, ofEpochSecond(timestamp + 1000))));
        when(mockVersionedResource.getInteractionModel()).thenReturn(LDP.RDFSource);
        when(mockVersionedResource.getModified()).thenReturn(time);
        when(mockVersionedResource.getBinary()).thenReturn(empty());
        when(mockVersionedResource.isMemento()).thenReturn(true);
        when(mockVersionedResource.getIdentifier()).thenReturn(identifier);
        when(mockVersionedResource.getInbox()).thenReturn(empty());
        when(mockVersionedResource.getAnnotationService()).thenReturn(empty());
        when(mockVersionedResource.getTypes()).thenReturn(emptyList());

        when(mockBinaryVersionedResource.getMementos()).thenReturn(
                asList(new VersionRange(ofEpochSecond(timestamp - 2000), ofEpochSecond(timestamp - 1000)),
                        new VersionRange(ofEpochSecond(timestamp - 1000), time),
                        new VersionRange(time, ofEpochSecond(timestamp + 1000))));
        when(mockBinaryVersionedResource.getInteractionModel()).thenReturn(LDP.NonRDFSource);
        when(mockBinaryVersionedResource.getModified()).thenReturn(time);
        when(mockBinaryVersionedResource.getBinary()).thenReturn(of(mockBinary));
        when(mockBinaryVersionedResource.isMemento()).thenReturn(true);
        when(mockBinaryVersionedResource.getIdentifier()).thenReturn(binaryIdentifier);
        when(mockBinaryVersionedResource.getInbox()).thenReturn(empty());
        when(mockBinaryVersionedResource.getAnnotationService()).thenReturn(empty());
        when(mockBinaryVersionedResource.getTypes()).thenReturn(emptyList());

        when(mockBinaryResource.getMementos()).thenReturn(emptyList());
        when(mockBinaryResource.getInteractionModel()).thenReturn(LDP.NonRDFSource);
        when(mockBinaryResource.getModified()).thenReturn(time);
        when(mockBinaryResource.getBinary()).thenReturn(of(mockBinary));
        when(mockBinaryResource.isMemento()).thenReturn(false);
        when(mockBinaryResource.getIdentifier()).thenReturn(binaryIdentifier);
        when(mockBinaryResource.getInbox()).thenReturn(empty());
        when(mockBinaryResource.getAnnotationService()).thenReturn(empty());
        when(mockBinaryResource.getTypes()).thenReturn(emptyList());

        when(mockBinary.getModified()).thenReturn(time);
        when(mockBinary.getIdentifier()).thenReturn(binaryInternalIdentifier);
        when(mockBinary.getMimeType()).thenReturn(of(BINARY_MIME_TYPE));
        when(mockBinary.getSize()).thenReturn(of(BINARY_SIZE));

        when(mockBinaryService.supportedAlgorithms()).thenReturn(new HashSet<>(asList("MD5", "SHA")));
        when(mockBinaryService.digest(eq("MD5"), any(InputStream.class))).thenReturn(of("md5-digest"));
        when(mockBinaryService.digest(eq("SHA"), any(InputStream.class))).thenReturn(of("sha1-digest"));
        when(mockBinaryService.getContent(eq(REPO1), eq(binaryInternalIdentifier)))
                .thenAnswer(x -> of(new ByteArrayInputStream("Some input stream".getBytes(UTF_8))));
        when(mockBinaryService.getResolver(eq(binaryInternalIdentifier))).thenReturn(of(mockBinaryResolver));
        when(mockBinaryService.getResolverForPartition(eq(REPO1))).thenReturn(of(mockBinaryResolver));
        when(mockBinaryService.getIdentifierSupplier(eq(REPO1))).thenReturn(() -> RANDOM_VALUE);

        when(mockBinaryResolver.supportsMultipartUpload()).thenReturn(false);

        when(mockResource.getMementos()).thenReturn(emptyList());
        when(mockResource.getInteractionModel()).thenReturn(LDP.RDFSource);
        when(mockResource.getModified()).thenReturn(time);
        when(mockResource.getBinary()).thenReturn(empty());
        when(mockResource.isMemento()).thenReturn(false);
        when(mockResource.getIdentifier()).thenReturn(identifier);
        when(mockResource.getInbox()).thenReturn(empty());
        when(mockResource.getAnnotationService()).thenReturn(empty());
        when(mockResource.getTypes()).thenReturn(emptyList());

        when(mockDeletedResource.getMementos()).thenReturn(emptyList());
        when(mockDeletedResource.getInteractionModel()).thenReturn(LDP.Resource);
        when(mockDeletedResource.getModified()).thenReturn(time);
        when(mockDeletedResource.getBinary()).thenReturn(empty());
        when(mockDeletedResource.isMemento()).thenReturn(false);
        when(mockDeletedResource.getIdentifier()).thenReturn(identifier);
        when(mockDeletedResource.getInbox()).thenReturn(empty());
        when(mockDeletedResource.getAnnotationService()).thenReturn(empty());
        when(mockDeletedResource.getTypes()).thenReturn(asList(Trellis.DeletedResource));

        when(mockUserDeletedResource.getMementos()).thenReturn(emptyList());
        when(mockUserDeletedResource.getInteractionModel()).thenReturn(LDP.Container);
        when(mockUserDeletedResource.getModified()).thenReturn(time);
        when(mockUserDeletedResource.getBinary()).thenReturn(empty());
        when(mockUserDeletedResource.isMemento()).thenReturn(false);
        when(mockUserDeletedResource.getIdentifier()).thenReturn(identifier);
        when(mockUserDeletedResource.getInbox()).thenReturn(empty());
        when(mockUserDeletedResource.getAnnotationService()).thenReturn(empty());
        when(mockUserDeletedResource.getTypes()).thenReturn(asList(Trellis.DeletedResource));

        when(mockResourceService.unskolemize(any(IRI.class))).thenAnswer(inv -> {
            final String uri = ((IRI) inv.getArgument(0)).getIRIString();
            if (uri.startsWith(TRELLIS_BNODE_PREFIX)) {
                return bnode;
            }
            return (IRI) inv.getArgument(0);
        });
        when(mockResourceService.toInternal(any(RDFTerm.class), any())).thenAnswer(inv -> {
            final RDFTerm term = (RDFTerm) inv.getArgument(0);
            if (term instanceof IRI) {
                final String iri = ((IRI) term).getIRIString();
                if (iri.startsWith(BASE_URL)) {
                    return rdf.createIRI(TRELLIS_PREFIX + iri.substring(BASE_URL.length()));
                }
            }
            return term;
        });
        when(mockResourceService.toExternal(any(RDFTerm.class), any())).thenAnswer(inv -> {
            final RDFTerm term = (RDFTerm) inv.getArgument(0);
            if (term instanceof IRI) {
                final String iri = ((IRI) term).getIRIString();
                if (iri.startsWith(TRELLIS_PREFIX)) {
                    return rdf.createIRI(BASE_URL + iri.substring(TRELLIS_PREFIX.length()));
                }
            }
            return term;
        });

        when(mockResourceService.unskolemize(any(Literal.class))).then(returnsFirstArg());
        when(mockResourceService.put(any(IRI.class), any(Dataset.class))).thenReturn(true);
        when(mockResourceService.skolemize(any(Literal.class))).then(returnsFirstArg());
        when(mockResourceService.skolemize(any(IRI.class))).then(returnsFirstArg());
        when(mockResourceService.skolemize(any(BlankNode.class))).thenAnswer(
                inv -> rdf.createIRI(TRELLIS_BNODE_PREFIX + ((BlankNode) inv.getArgument(0)).uniqueReference()));
        when(mockResource.stream()).thenAnswer(inv -> Stream.of(
                rdf.createQuad(Trellis.PreferUserManaged, identifier, DC.title, rdf.createLiteral("A title")),
                rdf.createQuad(Trellis.PreferServerManaged, identifier, DC.created,
                        rdf.createLiteral("2017-04-01T10:15:00Z", XSD.dateTime)),
                rdf.createQuad(Trellis.PreferAccessControl, identifier, type, ACL.Authorization),
                rdf.createQuad(Trellis.PreferAccessControl, identifier, ACL.mode, ACL.Control)));
    }

    /* ****************************** *
     *           HEAD Tests
     * ****************************** */
    @Test
    public void testHeadDefaultType() {
        final Response res = target(RESOURCE_PATH).request().head();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(TEXT_TURTLE_TYPE.isCompatible(res.getMediaType()));
        assertTrue(res.getMediaType().isCompatible(TEXT_TURTLE_TYPE));
    }

    /* ******************************* *
     *            GET Tests
     * ******************************* */
    @Test
    public void testGetJson() throws IOException {
        final Response res = target("/" + RESOURCE_PATH).request().accept("application/ld+json").get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(APPLICATION_LD_JSON_TYPE.isCompatible(res.getMediaType()));
        assertTrue(res.getMediaType().isCompatible(APPLICATION_LD_JSON_TYPE));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertNull(res.getHeaderString(ACCEPT_POST));
        assertEquals(APPLICATION_SPARQL_UPDATE, res.getHeaderString(ACCEPT_PATCH));
        assertTrue(res.hasEntity());

        final List<String> templates = res.getStringHeaders().get(LINK_TEMPLATE);
        assertEquals(2L, templates.size());
        assertTrue(templates.contains("<" + BASE_URL + RESOURCE_PATH + "{?subject,predicate,object}>; rel=\""
                + LDP.Resource.getIRIString() + "\""));
        assertTrue(templates.contains(
                "<" + BASE_URL + RESOURCE_PATH + "{?version}>; rel=\"" + Memento.Memento.getIRIString() + "\""));

        assertTrue(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        final List<String> varies = res.getStringHeaders().get(VARY);
        assertFalse(varies.contains(RANGE));
        assertFalse(varies.contains(WANT_DIGEST));
        assertTrue(varies.contains(ACCEPT_DATETIME));
        assertTrue(varies.contains(PREFER));

        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        final List<Map<String, Object>> obj = MAPPER.readValue(entity,
                new TypeReference<List<Map<String, Object>>>() {
                });

        assertEquals(1L, obj.size());

        @SuppressWarnings("unchecked")
        final List<Map<String, String>> titles = (List<Map<String, String>>) obj.get(0)
                .get(DC.title.getIRIString());

        final List<String> titleVals = titles.stream().map(x -> x.get("@value")).collect(toList());

        assertEquals(1L, titleVals.size());
        assertTrue(titleVals.contains("A title"));
    }

    @Test
    public void testGetDefaultType() {
        final Response res = target(RESOURCE_PATH).request().get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(TEXT_TURTLE_TYPE.isCompatible(res.getMediaType()));
        assertTrue(res.getMediaType().isCompatible(TEXT_TURTLE_TYPE));
    }

    @Test
    public void testGetDefaultType2() {
        final Response res = target("partition/resource").request().get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(TEXT_TURTLE_TYPE.isCompatible(res.getMediaType()));
        assertTrue(res.getMediaType().isCompatible(TEXT_TURTLE_TYPE));
    }

    @Test
    public void testScrewyPreferHeader() {
        final Response res = target(RESOURCE_PATH).request().header("Prefer", "wait=just one minute").get();

        assertEquals(BAD_REQUEST, res.getStatusInfo());
    }

    @Test
    public void testScrewyAcceptDatetimeHeader() {
        final Response res = target(RESOURCE_PATH).request().header("Accept-Datetime", "it's pathetic how we both")
                .get();

        assertEquals(BAD_REQUEST, res.getStatusInfo());
    }

    @Test
    public void testScrewyRange() {
        final Response res = target(BINARY_PATH).request().header("Range", "say it to my face, then").get();

        assertEquals(BAD_REQUEST, res.getStatusInfo());
    }

    @Test
    public void testGetRootSlash() {
        final Response res = target(REPO1 + "/").request().get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(TEXT_TURTLE_TYPE.isCompatible(res.getMediaType()));
        assertTrue(res.getMediaType().isCompatible(TEXT_TURTLE_TYPE));
    }

    @Test
    public void testGetRoot() {
        final Response res = target(REPO1).request().get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(TEXT_TURTLE_TYPE.isCompatible(res.getMediaType()));
        assertTrue(res.getMediaType().isCompatible(TEXT_TURTLE_TYPE));
    }

    @Test
    public void testGetDatetime() {
        final Response res = target(RESOURCE_PATH).request()
                .header(ACCEPT_DATETIME, RFC_1123_DATE_TIME.withZone(UTC).format(time)).get();

        assertEquals(OK, res.getStatusInfo());
        assertNotNull(res.getHeaderString(MEMENTO_DATETIME));
        assertEquals(time, parse(res.getHeaderString(MEMENTO_DATETIME), RFC_1123_DATE_TIME).toInstant());

        // Jersey's client doesn't parse complex link headers correctly
        final List<Link> links = res.getStringHeaders().get(LINK).stream().map(Link::valueOf).collect(toList());
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 2000))
                                .equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496260729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 1000))
                                .equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496261729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(time).equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496262729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("timemap")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 2000))
                                .equals(l.getParams().get("from"))
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp + 1000))
                                .equals(l.getParams().get("until"))
                        && APPLICATION_LINK_FORMAT.equals(l.getType())
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?ext=timemap")));
        assertTrue(links.stream().anyMatch(
                l -> l.getRels().contains("timegate") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(links.stream().anyMatch(
                l -> l.getRels().contains("original") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertFalse(links.stream().anyMatch(hasLink(rdf.createIRI(BASE_URL + RESOURCE_PATH + "?ext=upload"),
                Trellis.multipartUploadService.getIRIString())));
        assertTrue(links.stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(links.stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(links.stream().anyMatch(hasType(LDP.Container)));
    }

    @Test
    public void testGetTrailingSlash() {
        final Response res = target(RESOURCE_PATH + "/").request().get();

        assertEquals(OK, res.getStatusInfo());
        assertEquals(from(time), res.getLastModified());
        assertTrue(res.getLinks().stream().anyMatch(
                l -> l.getRel().contains("timegate") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(res.getLinks().stream().anyMatch(
                l -> l.getRel().contains("original") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
    }

    @Test
    public void testGetBinaryDescription() {
        final Response res = target(BINARY_PATH).request().accept("text/turtle").get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertTrue(res.getAllowedMethods().contains("POST"));

        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));

        assertTrue(res.getMediaType().isCompatible(TEXT_TURTLE_TYPE));
        assertNull(res.getHeaderString(ACCEPT_RANGES));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));

        final List<String> varies = res.getStringHeaders().get(VARY);
        assertFalse(varies.contains(RANGE));
        assertFalse(varies.contains(WANT_DIGEST));
        assertTrue(varies.contains(ACCEPT_DATETIME));
        assertTrue(varies.contains(PREFER));
    }

    @Test
    public void testGetBinary() throws IOException {
        final Response res = target(BINARY_PATH).request().get();

        assertEquals(OK, res.getStatusInfo());
        assertFalse(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertFalse(res.getLinks().stream().anyMatch(hasLink(rdf.createIRI(BASE_URL + BINARY_PATH + "?ext=upload"),
                Trellis.multipartUploadService.getIRIString())));

        assertTrue(res.getMediaType().isCompatible(TEXT_PLAIN_TYPE));
        assertNotNull(res.getHeaderString(ACCEPT_RANGES));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));

        final List<String> varies = res.getStringHeaders().get(VARY);
        assertTrue(varies.contains(RANGE));
        assertTrue(varies.contains(WANT_DIGEST));
        assertTrue(varies.contains(ACCEPT_DATETIME));
        assertFalse(varies.contains(PREFER));

        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        assertEquals("Some input stream", entity);
    }

    @Test
    public void testGetBinaryMetadataError1() {
        when(mockBinary.getModified()).thenReturn(null);
        final Response res = target(BINARY_PATH).request().get();

        assertEquals(INTERNAL_SERVER_ERROR, res.getStatusInfo());
    }

    @Test
    public void testGetBinaryMetadataError2() {
        when(mockBinary.getIdentifier()).thenReturn(null);
        final Response res = target(BINARY_PATH).request().get();

        assertEquals(INTERNAL_SERVER_ERROR, res.getStatusInfo());
    }

    @Test
    public void testGetBinaryDigestMd5() throws IOException {
        final Response res = target(BINARY_PATH).request().header(WANT_DIGEST, "MD5").get();

        assertEquals(OK, res.getStatusInfo());
        assertFalse(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));

        assertTrue(res.getMediaType().isCompatible(TEXT_PLAIN_TYPE));
        assertNotNull(res.getHeaderString(ACCEPT_RANGES));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
        assertEquals("md5-digest", res.getHeaderString(DIGEST));

        final List<String> varies = res.getStringHeaders().get(VARY);
        assertTrue(varies.contains(RANGE));
        assertTrue(varies.contains(WANT_DIGEST));
        assertTrue(varies.contains(ACCEPT_DATETIME));
        assertFalse(varies.contains(PREFER));

        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        assertEquals("Some input stream", entity);
    }

    @Test
    public void testGetBinaryDigestSha1() throws IOException {
        final Response res = target(BINARY_PATH).request().header(WANT_DIGEST, "SHA").get();

        assertEquals(OK, res.getStatusInfo());
        assertFalse(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));

        assertTrue(res.getMediaType().isCompatible(TEXT_PLAIN_TYPE));
        assertNotNull(res.getHeaderString(ACCEPT_RANGES));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
        assertEquals("sha1-digest", res.getHeaderString(DIGEST));

        final List<String> varies = res.getStringHeaders().get(VARY);
        assertTrue(varies.contains(RANGE));
        assertTrue(varies.contains(WANT_DIGEST));
        assertTrue(varies.contains(ACCEPT_DATETIME));
        assertFalse(varies.contains(PREFER));

        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        assertEquals("Some input stream", entity);
    }

    @Test
    public void testGetBinaryRange() throws IOException {
        final Response res = target(BINARY_PATH).request().header(RANGE, "bytes=3-10").get();

        assertEquals(OK, res.getStatusInfo());
        assertFalse(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));

        assertTrue(res.getMediaType().isCompatible(TEXT_PLAIN_TYPE));
        assertNotNull(res.getHeaderString(ACCEPT_RANGES));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));

        final List<String> varies = res.getStringHeaders().get(VARY);
        assertTrue(varies.contains(RANGE));
        assertTrue(varies.contains(WANT_DIGEST));
        assertTrue(varies.contains(ACCEPT_DATETIME));
        assertFalse(varies.contains(PREFER));

        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        assertEquals("e input", entity);
    }

    @Test
    public void testGetBinaryRangeExceed() throws IOException {
        final Response res = target(BINARY_PATH).request().header(RANGE, "bytes=300-400").get();

        assertEquals(OK, res.getStatusInfo());
        assertFalse(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));

        assertTrue(res.getMediaType().isCompatible(TEXT_PLAIN_TYPE));
        assertNotNull(res.getHeaderString(ACCEPT_RANGES));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));

        final List<String> varies = res.getStringHeaders().get(VARY);
        assertTrue(varies.contains(RANGE));
        assertTrue(varies.contains(WANT_DIGEST));
        assertTrue(varies.contains(ACCEPT_DATETIME));
        assertFalse(varies.contains(PREFER));

        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        assertEquals("", entity);
    }

    @Test
    public void testGetBinaryErrorSkip() throws IOException {
        when(mockBinaryService.getContent(eq(REPO1), eq(binaryInternalIdentifier))).thenReturn(of(mockInputStream));
        when(mockInputStream.skip(anyLong())).thenThrow(new IOException());
        final Response res = target(BINARY_PATH).request().header(RANGE, "bytes=300-400").get();
        assertEquals(INTERNAL_SERVER_ERROR, res.getStatusInfo());
    }

    @Test
    public void testGetBinaryDigestError() throws IOException {
        when(mockBinaryService.getContent(eq(REPO1), eq(binaryInternalIdentifier))).thenReturn(of(mockInputStream));
        doThrow(new IOException()).when(mockInputStream).close();
        final Response res = target(BINARY_PATH).request().header(WANT_DIGEST, "MD5").get();
        assertEquals(INTERNAL_SERVER_ERROR, res.getStatusInfo());
    }

    @Test
    public void testGetBinaryError() throws IOException {
        when(mockBinaryService.getContent(eq(REPO1), eq(binaryInternalIdentifier))).thenReturn(empty());
        final Response res = target(BINARY_PATH).request().get();
        assertEquals(INTERNAL_SERVER_ERROR, res.getStatusInfo());
    }

    @Test
    public void testGetVersionError() throws IOException {
        final Response res = target(BINARY_PATH).queryParam("version", "looking at my history").request().get();

        assertEquals(BAD_REQUEST, res.getStatusInfo());
    }

    @Test
    public void testGetBinaryVersion() throws IOException {
        final Response res = target(BINARY_PATH).queryParam("version", timestamp).request().get();

        assertEquals(OK, res.getStatusInfo());
        assertFalse(res.getAllowedMethods().contains("PATCH"));
        assertFalse(res.getAllowedMethods().contains("PUT"));
        assertFalse(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        // Jersey's client doesn't parse complex link headers correctly
        final List<Link> links = res.getStringHeaders().get(LINK).stream().map(Link::valueOf).collect(toList());

        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 2000))
                                .equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + BINARY_PATH + "?version=1496260729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 1000))
                                .equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + BINARY_PATH + "?version=1496261729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(time).equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + BINARY_PATH + "?version=1496262729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("timemap")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 2000))
                                .equals(l.getParams().get("from"))
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp + 1000))
                                .equals(l.getParams().get("until"))
                        && APPLICATION_LINK_FORMAT.equals(l.getType())
                        && l.getUri().toString().equals(BASE_URL + BINARY_PATH + "?ext=timemap")));
        assertTrue(links.stream().anyMatch(
                l -> l.getRels().contains("timegate") && l.getUri().toString().equals(BASE_URL + BINARY_PATH)));
        assertTrue(links.stream().anyMatch(
                l -> l.getRels().contains("original") && l.getUri().toString().equals(BASE_URL + BINARY_PATH)));
        assertTrue(links.stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(links.stream().anyMatch(hasType(LDP.NonRDFSource)));
        assertFalse(links.stream().anyMatch(hasType(LDP.Container)));

        assertTrue(res.getMediaType().isCompatible(TEXT_PLAIN_TYPE));
        assertEquals("bytes", res.getHeaderString(ACCEPT_RANGES));
        assertNotNull(res.getHeaderString(MEMENTO_DATETIME));
        assertEquals(time, parse(res.getHeaderString(MEMENTO_DATETIME), RFC_1123_DATE_TIME).toInstant());

        final List<String> varies = res.getStringHeaders().get(VARY);
        assertTrue(varies.contains(RANGE));
        assertTrue(varies.contains(WANT_DIGEST));
        assertFalse(varies.contains(ACCEPT_DATETIME));
        assertFalse(varies.contains(PREFER));

        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        assertEquals("Some input stream", entity);
    }

    @Test
    public void testPrefer() throws IOException {
        final Response res = target(RESOURCE_PATH).request()
                .header("Prefer",
                        "return=representation; include=\"" + Trellis.PreferServerManaged.getIRIString() + "\"")
                .accept("application/ld+json; profile=\"http://www.w3.org/ns/json-ld#compacted\"").get();

        assertEquals(OK, res.getStatusInfo());

        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        final Map<String, Object> obj = MAPPER.readValue(entity, new TypeReference<Map<String, Object>>() {
        });

        assertTrue(obj.containsKey("@context"));
        assertTrue(obj.containsKey("title"));
        assertFalse(obj.containsKey("mode"));
        assertTrue(obj.containsKey("created"));

        assertEquals("A title", (String) obj.get("title"));
    }

    @Test
    public void testGetJsonCompact() throws IOException {
        final Response res = target(RESOURCE_PATH).request()
                .accept("application/ld+json; profile=\"http://www.w3.org/ns/json-ld#compacted\"").get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(APPLICATION_LD_JSON_TYPE.isCompatible(res.getMediaType()));
        assertTrue(res.getMediaType().isCompatible(APPLICATION_LD_JSON_TYPE));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertNull(res.getHeaderString(ACCEPT_POST));
        assertEquals(APPLICATION_SPARQL_UPDATE, res.getHeaderString(ACCEPT_PATCH));
        assertNull(res.getHeaderString(ACCEPT_RANGES));
        assertEquals(from(time), res.getLastModified());
        assertTrue(res.getLinks().stream().anyMatch(
                l -> l.getRel().contains("timegate") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(res.getLinks().stream().anyMatch(
                l -> l.getRel().contains("original") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(res.hasEntity());

        assertTrue(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        final List<String> varies = res.getStringHeaders().get(VARY);
        assertFalse(varies.contains(RANGE));
        assertFalse(varies.contains(WANT_DIGEST));
        assertTrue(varies.contains(ACCEPT_DATETIME));
        assertTrue(varies.contains(PREFER));

        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        final Map<String, Object> obj = MAPPER.readValue(entity, new TypeReference<Map<String, Object>>() {
        });

        assertTrue(obj.containsKey("@context"));
        assertTrue(obj.containsKey("title"));
        assertFalse(obj.containsKey("mode"));
        assertFalse(obj.containsKey("created"));

        assertEquals("A title", (String) obj.get("title"));
    }

    @Test
    public void testGetJsonCompactLDF1() throws IOException {
        when(mockResource.stream()).thenAnswer(inv -> Stream.of(
                rdf.createQuad(Trellis.PreferUserManaged, identifier, DC.creator, rdf.createLiteral("User")),
                rdf.createQuad(Trellis.PreferUserManaged, rdf.createIRI("ex:foo"), DC.title,
                        rdf.createIRI("ex:title")),
                rdf.createQuad(Trellis.PreferUserManaged, identifier, DC.title, rdf.createLiteral("A title")),
                rdf.createQuad(Trellis.PreferUserManaged, identifier, DC.title, rdf.createLiteral("Other title")),
                rdf.createQuad(Trellis.PreferUserManaged, identifier, type, rdf.createIRI("ex:Type")),
                rdf.createQuad(Trellis.PreferServerManaged, identifier, DC.created,
                        rdf.createLiteral("2017-04-01T10:15:00Z", XSD.dateTime)),
                rdf.createQuad(Trellis.PreferAccessControl, identifier, type, ACL.Authorization),
                rdf.createQuad(Trellis.PreferAccessControl, identifier, ACL.mode, ACL.Control)));

        final Response res = target(RESOURCE_PATH).queryParam("subject", BASE_URL + RESOURCE_PATH)
                .queryParam("predicate", "http://purl.org/dc/terms/title").request()
                .accept("application/ld+json; profile=\"http://www.w3.org/ns/json-ld#compacted\"").get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(APPLICATION_LD_JSON_TYPE.isCompatible(res.getMediaType()));
        assertTrue(res.getMediaType().isCompatible(APPLICATION_LD_JSON_TYPE));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertNull(res.getHeaderString(ACCEPT_POST));
        assertEquals(APPLICATION_SPARQL_UPDATE, res.getHeaderString(ACCEPT_PATCH));
        assertNull(res.getHeaderString(ACCEPT_RANGES));
        assertEquals(from(time), res.getLastModified());
        assertTrue(res.getLinks().stream().anyMatch(
                l -> l.getRel().contains("timegate") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(res.getLinks().stream().anyMatch(
                l -> l.getRel().contains("original") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(res.hasEntity());

        assertTrue(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        final List<String> varies = res.getStringHeaders().get(VARY);
        assertFalse(varies.contains(RANGE));
        assertFalse(varies.contains(WANT_DIGEST));
        assertTrue(varies.contains(ACCEPT_DATETIME));
        assertTrue(varies.contains(PREFER));

        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        final Map<String, Object> obj = MAPPER.readValue(entity, new TypeReference<Map<String, Object>>() {
        });

        assertTrue(obj.containsKey("@context"));
        assertFalse(obj.containsKey("creator"));
        assertTrue(obj.containsKey("title"));
        assertFalse(obj.containsKey("mode"));
        assertFalse(obj.containsKey("created"));

        assertTrue(obj.get("title") instanceof List);
        @SuppressWarnings("unchecked")
        final List<Object> titles = (List<Object>) obj.get("title");
        assertTrue(titles.contains("A title"));
        assertEquals(2L, titles.size());
        assertEquals(BASE_URL + RESOURCE_PATH, obj.get("@id"));
    }

    @Test
    public void testGetJsonCompactLDF2() throws IOException {
        when(mockResource.stream()).thenAnswer(inv -> Stream.of(
                rdf.createQuad(Trellis.PreferUserManaged, identifier, DC.creator, rdf.createLiteral("User")),
                rdf.createQuad(Trellis.PreferUserManaged, rdf.createIRI("ex:foo"), DC.title,
                        rdf.createIRI("ex:title")),
                rdf.createQuad(Trellis.PreferUserManaged, identifier, DC.title, rdf.createLiteral("A title")),
                rdf.createQuad(Trellis.PreferUserManaged, identifier, DC.title, rdf.createLiteral("Other title")),
                rdf.createQuad(Trellis.PreferUserManaged, identifier, type, rdf.createIRI("ex:Type")),
                rdf.createQuad(Trellis.PreferUserManaged, rdf.createIRI("ex:foo"), type, rdf.createIRI("ex:Type")),
                rdf.createQuad(Trellis.PreferUserManaged, rdf.createIRI("ex:foo"), type, rdf.createIRI("ex:Other")),
                rdf.createQuad(Trellis.PreferServerManaged, identifier, DC.created,
                        rdf.createLiteral("2017-04-01T10:15:00Z", XSD.dateTime)),
                rdf.createQuad(Trellis.PreferAccessControl, identifier, type, ACL.Authorization),
                rdf.createQuad(Trellis.PreferAccessControl, identifier, ACL.mode, ACL.Control)));

        final Response res = target(RESOURCE_PATH).queryParam("subject", BASE_URL + RESOURCE_PATH)
                .queryParam("object", "ex:Type").queryParam("predicate", "").request()
                .accept("application/ld+json; profile=\"http://www.w3.org/ns/json-ld#compacted\"").get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(APPLICATION_LD_JSON_TYPE.isCompatible(res.getMediaType()));
        assertTrue(res.getMediaType().isCompatible(APPLICATION_LD_JSON_TYPE));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertNull(res.getHeaderString(ACCEPT_POST));
        assertEquals(APPLICATION_SPARQL_UPDATE, res.getHeaderString(ACCEPT_PATCH));
        assertNull(res.getHeaderString(ACCEPT_RANGES));
        assertEquals(from(time), res.getLastModified());
        assertTrue(res.getLinks().stream().anyMatch(
                l -> l.getRel().contains("timegate") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(res.getLinks().stream().anyMatch(
                l -> l.getRel().contains("original") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(res.hasEntity());

        assertTrue(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        final List<String> varies = res.getStringHeaders().get(VARY);
        assertFalse(varies.contains(RANGE));
        assertFalse(varies.contains(WANT_DIGEST));
        assertTrue(varies.contains(ACCEPT_DATETIME));
        assertTrue(varies.contains(PREFER));

        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        final Map<String, Object> obj = MAPPER.readValue(entity, new TypeReference<Map<String, Object>>() {
        });

        assertTrue(obj.containsKey("@type"));
        assertFalse(obj.containsKey("@context"));
        assertFalse(obj.containsKey("creator"));
        assertFalse(obj.containsKey("title"));
        assertFalse(obj.containsKey("mode"));
        assertFalse(obj.containsKey("created"));

        assertEquals("ex:Type", obj.get("@type"));
        assertEquals(BASE_URL + RESOURCE_PATH, obj.get("@id"));
    }

    @Test
    public void testGetJsonCompactLDF3() throws IOException {
        when(mockResource.stream()).thenAnswer(inv -> Stream.of(
                rdf.createQuad(Trellis.PreferUserManaged, identifier, DC.creator, rdf.createLiteral("User")),
                rdf.createQuad(Trellis.PreferUserManaged, rdf.createIRI("ex:foo"), DC.title,
                        rdf.createIRI("ex:title")),
                rdf.createQuad(Trellis.PreferUserManaged, identifier, DC.title, rdf.createLiteral("A title")),
                rdf.createQuad(Trellis.PreferUserManaged, identifier, DC.title, rdf.createLiteral("Other title")),
                rdf.createQuad(Trellis.PreferUserManaged, identifier, type, rdf.createIRI("ex:Type")),
                rdf.createQuad(Trellis.PreferUserManaged, rdf.createIRI("ex:foo"), type, rdf.createIRI("ex:Type")),
                rdf.createQuad(Trellis.PreferUserManaged, rdf.createIRI("ex:foo"), type, rdf.createIRI("ex:Other")),
                rdf.createQuad(Trellis.PreferServerManaged, identifier, DC.created,
                        rdf.createLiteral("2017-04-01T10:15:00Z", XSD.dateTime)),
                rdf.createQuad(Trellis.PreferAccessControl, identifier, type, ACL.Authorization),
                rdf.createQuad(Trellis.PreferAccessControl, identifier, ACL.mode, ACL.Control)));

        final Response res = target(RESOURCE_PATH).queryParam("subject", BASE_URL + RESOURCE_PATH)
                .queryParam("object", "A title").queryParam("predicate", DC.title.getIRIString()).request()
                .accept("application/ld+json; profile=\"http://www.w3.org/ns/json-ld#compacted\"").get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(APPLICATION_LD_JSON_TYPE.isCompatible(res.getMediaType()));
        assertTrue(res.getMediaType().isCompatible(APPLICATION_LD_JSON_TYPE));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertNull(res.getHeaderString(ACCEPT_POST));
        assertEquals(APPLICATION_SPARQL_UPDATE, res.getHeaderString(ACCEPT_PATCH));
        assertNull(res.getHeaderString(ACCEPT_RANGES));
        assertEquals(from(time), res.getLastModified());
        assertTrue(res.getLinks().stream().anyMatch(
                l -> l.getRel().contains("timegate") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(res.getLinks().stream().anyMatch(
                l -> l.getRel().contains("original") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(res.hasEntity());

        assertTrue(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        final List<String> varies = res.getStringHeaders().get(VARY);
        assertFalse(varies.contains(RANGE));
        assertFalse(varies.contains(WANT_DIGEST));
        assertTrue(varies.contains(ACCEPT_DATETIME));
        assertTrue(varies.contains(PREFER));

        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        final Map<String, Object> obj = MAPPER.readValue(entity, new TypeReference<Map<String, Object>>() {
        });

        assertFalse(obj.containsKey("@type"));
        assertTrue(obj.containsKey("@context"));
        assertFalse(obj.containsKey("creator"));
        assertTrue(obj.containsKey("title"));
        assertFalse(obj.containsKey("mode"));
        assertFalse(obj.containsKey("created"));

        assertEquals("A title", obj.get("title"));
        assertEquals(BASE_URL + RESOURCE_PATH, obj.get("@id"));
    }

    @Test
    public void testGetTimeMapLinkDefaultFormat() throws IOException {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);

        final Response res = target(RESOURCE_PATH).queryParam("ext", "timemap").request().get();

        assertEquals(OK, res.getStatusInfo());
        assertEquals(MediaType.valueOf(APPLICATION_LINK_FORMAT), res.getMediaType());
    }

    @Test
    public void testGetTimeMapLinkDefaultFormat2() throws IOException {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);

        final Response res = target("partition/resource").queryParam("ext", "timemap").request().get();

        assertEquals(OK, res.getStatusInfo());
        assertEquals(MediaType.valueOf(APPLICATION_LINK_FORMAT), res.getMediaType());
    }

    @Test
    public void testGetTimeMapLinkInvalidFormat() throws IOException {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);

        final Response res = target(RESOURCE_PATH).queryParam("ext", "timemap").request()
                .accept("some/made-up-format").get();

        assertEquals(NOT_ACCEPTABLE, res.getStatusInfo());
    }

    @Test
    public void testGetTimeMapLink() throws IOException {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);
        when(mockResource.getMementos()).thenReturn(
                asList(new VersionRange(ofEpochSecond(timestamp - 2000), ofEpochSecond(timestamp - 1000)),
                        new VersionRange(ofEpochSecond(timestamp - 1000), time),
                        new VersionRange(time, ofEpochSecond(timestamp + 1000))));

        final Response res = target(RESOURCE_PATH).queryParam("ext", "timemap").request()
                .accept(APPLICATION_LINK_FORMAT).get();

        assertEquals(OK, res.getStatusInfo());
        assertEquals(MediaType.valueOf(APPLICATION_LINK_FORMAT), res.getMediaType());
        assertNull(res.getHeaderString(ACCEPT_POST));
        assertNull(res.getHeaderString(ACCEPT_PATCH));
        assertNull(res.getHeaderString(ACCEPT_RANGES));
        assertNull(res.getLastModified());

        // Jersey's client doesn't parse complex link headers correctly, so res.getLinks() is not used here
        final List<Link> links = res.getStringHeaders().get(LINK).stream().map(Link::valueOf).collect(toList());

        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 2000))
                                .equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496260729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 1000))
                                .equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496261729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(time).equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496262729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("timemap")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 2000))
                                .equals(l.getParams().get("from"))
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp + 1000))
                                .equals(l.getParams().get("until"))
                        && APPLICATION_LINK_FORMAT.equals(l.getType())
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?ext=timemap")));
        assertTrue(links.stream().anyMatch(
                l -> l.getRels().contains("timegate") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(links.stream().anyMatch(
                l -> l.getRels().contains("original") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(links.stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(links.stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(links.stream().anyMatch(hasType(LDP.Container)));

        assertFalse(res.getAllowedMethods().contains("PATCH"));
        assertFalse(res.getAllowedMethods().contains("PUT"));
        assertFalse(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));

        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        final List<Link> entityLinks = stream(entity.split(",\n")).map(Link::valueOf).collect(toList());
        assertEquals(4L, entityLinks.size());
        entityLinks.forEach(l -> assertTrue(links.contains(l)));
    }

    @Test
    public void testGetTimeMapJsonCompact() throws IOException {
        when(mockResource.getMementos()).thenReturn(
                asList(new VersionRange(ofEpochSecond(timestamp - 2000), ofEpochSecond(timestamp - 1000)),
                        new VersionRange(ofEpochSecond(timestamp - 1000), time),
                        new VersionRange(time, ofEpochSecond(timestamp + 1000))));

        final Response res = target(RESOURCE_PATH).queryParam("ext", "timemap").request()
                .accept("application/ld+json; profile=\"http://www.w3.org/ns/json-ld#compacted\"").get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(APPLICATION_LD_JSON_TYPE.isCompatible(res.getMediaType()));
        assertTrue(res.getMediaType().isCompatible(APPLICATION_LD_JSON_TYPE));
        assertNull(res.getHeaderString(ACCEPT_POST));
        assertNull(res.getHeaderString(ACCEPT_PATCH));
        assertNull(res.getHeaderString(ACCEPT_RANGES));
        assertNull(res.getLastModified());

        // Jersey's client doesn't parse complex link headers correctly, so res.getLinks() is not used here
        final List<Link> links = res.getStringHeaders().get(LINK).stream().map(Link::valueOf).collect(toList());

        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 2000))
                                .equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496260729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 1000))
                                .equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496261729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(time).equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496262729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("timemap")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 2000))
                                .equals(l.getParams().get("from"))
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp + 1000))
                                .equals(l.getParams().get("until"))
                        && APPLICATION_LINK_FORMAT.equals(l.getType())
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?ext=timemap")));
        assertTrue(links.stream().anyMatch(
                l -> l.getRels().contains("timegate") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(links.stream().anyMatch(
                l -> l.getRels().contains("original") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(links.stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(links.stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(links.stream().anyMatch(hasType(LDP.Container)));

        assertFalse(res.getAllowedMethods().contains("PATCH"));
        assertFalse(res.getAllowedMethods().contains("PUT"));
        assertFalse(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));

        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        final Map<String, Object> obj = MAPPER.readValue(entity, new TypeReference<Map<String, Object>>() {
        });

        @SuppressWarnings("unchecked")
        final List<Map<String, Object>> graph = (List<Map<String, Object>>) obj.get("@graph");

        assertEquals(5L, graph.size());
        assertTrue(
                graph.stream().anyMatch(x -> x.containsKey("@id") && x.get("@id").equals(BASE_URL + RESOURCE_PATH)
                        && x.containsKey("timegate") && x.containsKey("timemap") && x.containsKey("memento")));
        assertTrue(graph.stream().anyMatch(
                x -> x.containsKey("@id") && x.get("@id").equals(BASE_URL + RESOURCE_PATH + "?ext=timemap")
                        && x.containsKey("hasBeginning") && x.containsKey("hasEnd")));
        assertTrue(graph.stream()
                .anyMatch(x -> x.containsKey("@id")
                        && x.get("@id").equals(BASE_URL + RESOURCE_PATH + "?version=1496260729000")
                        && x.containsKey("hasTime")));
        assertTrue(graph.stream()
                .anyMatch(x -> x.containsKey("@id")
                        && x.get("@id").equals(BASE_URL + RESOURCE_PATH + "?version=1496261729000")
                        && x.containsKey("hasTime")));
        assertTrue(graph.stream()
                .anyMatch(x -> x.containsKey("@id")
                        && x.get("@id").equals(BASE_URL + RESOURCE_PATH + "?version=1496262729000")
                        && x.containsKey("hasTime")));
    }

    @Test
    public void testGetTimeMapJson() throws IOException {
        when(mockResource.getMementos()).thenReturn(
                asList(new VersionRange(ofEpochSecond(timestamp - 2000), ofEpochSecond(timestamp - 1000)),
                        new VersionRange(ofEpochSecond(timestamp - 1000), time),
                        new VersionRange(time, ofEpochSecond(timestamp + 1000))));

        final Response res = target(RESOURCE_PATH).queryParam("ext", "timemap").request()
                .accept("application/ld+json; profile=\"http://www.w3.org/ns/json-ld#expanded\"").get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(APPLICATION_LD_JSON_TYPE.isCompatible(res.getMediaType()));
        assertTrue(res.getMediaType().isCompatible(APPLICATION_LD_JSON_TYPE));
        assertNull(res.getHeaderString(ACCEPT_POST));
        assertNull(res.getHeaderString(ACCEPT_PATCH));
        assertNull(res.getHeaderString(ACCEPT_RANGES));
        assertNull(res.getLastModified());

        // Jersey's client doesn't parse complex link headers correctly, so res.getLinks() is not used here
        final List<Link> links = res.getStringHeaders().get(LINK).stream().map(Link::valueOf).collect(toList());

        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 2000))
                                .equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496260729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 1000))
                                .equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496261729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(time).equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496262729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("timemap")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 2000))
                                .equals(l.getParams().get("from"))
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp + 1000))
                                .equals(l.getParams().get("until"))
                        && APPLICATION_LINK_FORMAT.equals(l.getType())
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?ext=timemap")));
        assertTrue(links.stream().anyMatch(
                l -> l.getRels().contains("timegate") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(links.stream().anyMatch(
                l -> l.getRels().contains("original") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(links.stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(links.stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(links.stream().anyMatch(hasType(LDP.Container)));

        assertFalse(res.getAllowedMethods().contains("PATCH"));
        assertFalse(res.getAllowedMethods().contains("PUT"));
        assertFalse(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));

        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        final List<Map<String, Object>> obj = MAPPER.readValue(entity,
                new TypeReference<List<Map<String, Object>>>() {
                });

        assertEquals(5L, obj.size());
        assertTrue(obj.stream()
                .anyMatch(x -> x.containsKey("@id") && x.get("@id").equals(BASE_URL + RESOURCE_PATH)
                        && x.containsKey("http://mementoweb.org/ns#timegate")
                        && x.containsKey("http://mementoweb.org/ns#timemap")
                        && x.containsKey("http://mementoweb.org/ns#memento")));
        assertTrue(obj.stream().anyMatch(
                x -> x.containsKey("@id") && x.get("@id").equals(BASE_URL + RESOURCE_PATH + "?ext=timemap")
                        && x.containsKey("http://www.w3.org/2006/time#hasBeginning")
                        && x.containsKey("http://www.w3.org/2006/time#hasEnd")));
        assertTrue(obj.stream()
                .anyMatch(x -> x.containsKey("@id")
                        && x.get("@id").equals(BASE_URL + RESOURCE_PATH + "?version=1496260729000")
                        && x.containsKey("http://www.w3.org/2006/time#hasTime")));
        assertTrue(obj.stream()
                .anyMatch(x -> x.containsKey("@id")
                        && x.get("@id").equals(BASE_URL + RESOURCE_PATH + "?version=1496261729000")
                        && x.containsKey("http://www.w3.org/2006/time#hasTime")));
        assertTrue(obj.stream()
                .anyMatch(x -> x.containsKey("@id")
                        && x.get("@id").equals(BASE_URL + RESOURCE_PATH + "?version=1496262729000")
                        && x.containsKey("http://www.w3.org/2006/time#hasTime")));
    }

    @Test
    public void testGetVersionJson() throws IOException {
        final Response res = target(RESOURCE_PATH).queryParam("version", timestamp).request()
                .accept("application/ld+json; profile=\"http://www.w3.org/ns/json-ld#compacted\"").get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(APPLICATION_LD_JSON_TYPE.isCompatible(res.getMediaType()));
        assertTrue(res.getMediaType().isCompatible(APPLICATION_LD_JSON_TYPE));
        assertNull(res.getHeaderString(ACCEPT_POST));
        assertNull(res.getHeaderString(ACCEPT_PATCH));
        assertNull(res.getHeaderString(ACCEPT_RANGES));
        assertEquals(from(time), res.getLastModified());

        // Jersey's client doesn't parse complex link headers correctly, so res.getLinks() is not used here
        final List<Link> links = res.getStringHeaders().get(LINK).stream().map(Link::valueOf).collect(toList());

        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 2000))
                                .equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496260729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 1000))
                                .equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496261729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(time).equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496262729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("timemap")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 2000))
                                .equals(l.getParams().get("from"))
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp + 1000))
                                .equals(l.getParams().get("until"))
                        && APPLICATION_LINK_FORMAT.equals(l.getType())
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?ext=timemap")));
        assertTrue(links.stream().anyMatch(
                l -> l.getRels().contains("timegate") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(links.stream().anyMatch(
                l -> l.getRels().contains("original") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(links.stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(links.stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(links.stream().anyMatch(hasType(LDP.Container)));

        assertFalse(res.getAllowedMethods().contains("PATCH"));
        assertFalse(res.getAllowedMethods().contains("PUT"));
        assertFalse(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));
        assertEquals(time, parse(res.getHeaderString(MEMENTO_DATETIME), RFC_1123_DATE_TIME).toInstant());
    }

    @Test
    public void testGetVersionContainerJson() throws IOException {
        when(mockVersionedResource.getInteractionModel()).thenReturn(LDP.Container);
        final Response res = target(RESOURCE_PATH).queryParam("version", timestamp).request()
                .accept("application/ld+json; profile=\"http://www.w3.org/ns/json-ld#compacted\"").get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(APPLICATION_LD_JSON_TYPE.isCompatible(res.getMediaType()));
        assertTrue(res.getMediaType().isCompatible(APPLICATION_LD_JSON_TYPE));
        assertNull(res.getHeaderString(ACCEPT_POST));
        assertNull(res.getHeaderString(ACCEPT_PATCH));
        assertNull(res.getHeaderString(ACCEPT_RANGES));
        assertEquals(from(time), res.getLastModified());

        // Jersey's client doesn't parse complex link headers correctly, so res.getLinks() is not used here
        final List<Link> links = res.getStringHeaders().get(LINK).stream().map(Link::valueOf).collect(toList());

        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 2000))
                                .equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496260729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 1000))
                                .equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496261729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("memento")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(time).equals(l.getParams().get("datetime"))
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?version=1496262729000")));
        assertTrue(links.stream()
                .anyMatch(l -> l.getRels().contains("timemap")
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp - 2000))
                                .equals(l.getParams().get("from"))
                        && RFC_1123_DATE_TIME.withZone(UTC).format(ofEpochSecond(timestamp + 1000))
                                .equals(l.getParams().get("until"))
                        && APPLICATION_LINK_FORMAT.equals(l.getType())
                        && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH + "?ext=timemap")));
        assertTrue(links.stream().anyMatch(
                l -> l.getRels().contains("timegate") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(links.stream().anyMatch(
                l -> l.getRels().contains("original") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertTrue(links.stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(links.stream().anyMatch(hasType(LDP.RDFSource)));
        assertTrue(links.stream().anyMatch(hasType(LDP.Container)));

        assertFalse(res.getAllowedMethods().contains("PATCH"));
        assertFalse(res.getAllowedMethods().contains("PUT"));
        assertFalse(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));
        assertEquals(time, parse(res.getHeaderString(MEMENTO_DATETIME), RFC_1123_DATE_TIME).toInstant());
    }

    @Test
    public void testGetNoAcl() {
        final Response res = target(RESOURCE_PATH).queryParam("ext", "acl").request().get();

        assertEquals(NOT_FOUND, res.getStatusInfo());
    }

    @Test
    public void testGetAclJsonCompact() throws IOException {
        when(mockResource.hasAcl()).thenReturn(true);
        final Response res = target(RESOURCE_PATH).queryParam("ext", "acl").request()
                .accept("application/ld+json; profile=\"http://www.w3.org/ns/json-ld#compacted\"").get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(APPLICATION_LD_JSON_TYPE.isCompatible(res.getMediaType()));
        assertTrue(res.getMediaType().isCompatible(APPLICATION_LD_JSON_TYPE));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertNull(res.getHeaderString(ACCEPT_POST));
        assertEquals(APPLICATION_SPARQL_UPDATE, res.getHeaderString(ACCEPT_PATCH));
        assertNull(res.getHeaderString(ACCEPT_RANGES));
        assertEquals(from(time), res.getLastModified());
        // The next two assertions may change at some point
        assertFalse(res.getLinks().stream().anyMatch(
                l -> l.getRel().contains("timegate") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));
        assertFalse(res.getLinks().stream().anyMatch(
                l -> l.getRel().contains("original") && l.getUri().toString().equals(BASE_URL + RESOURCE_PATH)));

        assertTrue(res.hasEntity());
        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        final Map<String, Object> obj = MAPPER.readValue(entity, new TypeReference<Map<String, Object>>() {
        });

        assertTrue(obj.containsKey("@context"));
        assertFalse(obj.containsKey("title"));
        assertTrue(obj.containsKey("mode"));
        assertEquals(ACL.Control.getIRIString(), (String) obj.get("mode"));

        assertTrue(res.getAllowedMethods().contains("PATCH"));
        assertFalse(res.getAllowedMethods().contains("PUT"));
        assertFalse(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        final List<String> varies = res.getStringHeaders().get(VARY);
        assertFalse(varies.contains(RANGE));
        assertFalse(varies.contains(WANT_DIGEST));
        assertTrue(varies.contains(ACCEPT_DATETIME));
        assertFalse(varies.contains(PREFER));
    }

    @Test
    public void testGetUserDeleted() {
        // Just setting the Trellis.DeletedResource type shouldn't, itself, lead to a 410 GONE response
        final Response res = target(USER_DELETED_PATH).request().get();

        assertEquals(OK, res.getStatusInfo());
    }

    @Test
    public void testGetLdpResource() {
        when(mockDeletedResource.getTypes()).thenReturn(emptyList());
        final Response res = target(DELETED_PATH).request().get();

        assertEquals(OK, res.getStatusInfo());
    }

    @Test
    public void testGetNotFound() {
        final Response res = target(NON_EXISTENT_PATH).request().get();

        assertEquals(NOT_FOUND, res.getStatusInfo());
    }

    @Test
    public void testGetGone() {
        final Response res = target(DELETED_PATH).request().get();

        assertEquals(GONE, res.getStatusInfo());
    }

    @Test
    public void testGetCORSInvalid() {
        final Response res = target(RESOURCE_PATH).request().header("Origin", "http://foo.com")
                .header("Access-Control-Request-Method", "PUT")
                .header("Access-Control-Request-Headers", "Content-Type, Link").get();

        assertEquals(OK, res.getStatusInfo());
        assertNull(res.getHeaderString("Access-Control-Allow-Origin"));
        assertNull(res.getHeaderString("Access-Control-Allow-Credentials"));
        assertNull(res.getHeaderString("Access-Control-Max-Age"));

        assertNull(res.getHeaderString("Access-Control-Allow-Headers"));
        assertNull(res.getHeaderString("Access-Control-Allow-Methods"));
    }

    @Test
    public void testGetCORS() {
        final String baseUri = getBaseUri().toString();
        final String origin = baseUri.substring(0, baseUri.length() - 1);
        final Response res = target(RESOURCE_PATH).request().header("Origin", origin)
                .header("Access-Control-Request-Method", "PUT")
                .header("Access-Control-Request-Headers", "Content-Type, Link").get();

        assertEquals(OK, res.getStatusInfo());
        assertEquals(origin, res.getHeaderString("Access-Control-Allow-Origin"));
        assertEquals("true", res.getHeaderString("Access-Control-Allow-Credentials"));
        assertNull(res.getHeaderString("Access-Control-Max-Age"));
        assertNull(res.getHeaderString("Access-Control-Allow-Headers"));
        assertNull(res.getHeaderString("Access-Control-Allow-Methods"));
    }

    @Test
    public void testGetCORSSimple() {
        final String baseUri = getBaseUri().toString();
        final String origin = baseUri.substring(0, baseUri.length() - 1);
        final Response res = target(RESOURCE_PATH).request().header("Origin", origin)
                .header("Access-Control-Request-Method", "POST").header("Access-Control-Request-Headers", "Accept")
                .get();

        assertEquals(OK, res.getStatusInfo());
        assertEquals(origin, res.getHeaderString("Access-Control-Allow-Origin"));
        assertEquals("true", res.getHeaderString("Access-Control-Allow-Credentials"));
        assertNull(res.getHeaderString("Access-Control-Max-Age"));
        assertNull(res.getHeaderString("Access-Control-Allow-Methods"));
        assertNull(res.getHeaderString("Access-Control-Allow-Headers"));
    }

    /* ******************************* *
     *            OPTIONS Tests
     * ******************************* */
    @Test
    public void testOptionsLDPRS() {
        final Response res = target(RESOURCE_PATH).request().options();

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertNull(res.getHeaderString(MEMENTO_DATETIME));

        assertTrue(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        assertEquals(APPLICATION_SPARQL_UPDATE, res.getHeaderString(ACCEPT_PATCH));

        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
    }

    @Test
    public void testOptionsLDPNR() {
        final Response res = target(BINARY_PATH).request().options();

        assertEquals(NO_CONTENT, res.getStatusInfo());

        assertTrue(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        assertEquals(APPLICATION_SPARQL_UPDATE, res.getHeaderString(ACCEPT_PATCH));
        assertNull(res.getHeaderString(ACCEPT_POST));

        assertNull(res.getHeaderString(MEMENTO_DATETIME));

        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
    }

    @Test
    public void testOptionsLDPC() {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);
        final Response res = target(RESOURCE_PATH).request().options();

        assertEquals(NO_CONTENT, res.getStatusInfo());

        assertTrue(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertTrue(res.getAllowedMethods().contains("POST"));

        assertEquals(APPLICATION_SPARQL_UPDATE, res.getHeaderString(ACCEPT_PATCH));
        assertNotNull(res.getHeaderString(ACCEPT_POST));
        final List<String> acceptPost = asList(res.getHeaderString(ACCEPT_POST).split(","));
        assertEquals(3L, acceptPost.size());
        assertTrue(acceptPost.contains("text/turtle"));
        assertTrue(acceptPost.contains(APPLICATION_LD_JSON));
        assertTrue(acceptPost.contains(APPLICATION_N_TRIPLES));

        assertNull(res.getHeaderString(MEMENTO_DATETIME));

        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
    }

    @Test
    public void testOptionsACL() {
        final Response res = target(RESOURCE_PATH).queryParam("ext", "acl").request().options();

        assertEquals(NO_CONTENT, res.getStatusInfo());

        assertTrue(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        assertEquals(APPLICATION_SPARQL_UPDATE, res.getHeaderString(ACCEPT_PATCH));
        assertNull(res.getHeaderString(ACCEPT_POST));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testOptionsPreflightInvalid() {
        final Response res = target(RESOURCE_PATH).request().header("Origin", "http://foo.com")
                .header("Access-Control-Request-Method", "PUT")
                .header("Access-Control-Request-Headers", "Content-Type, Link").options();

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertNull(res.getHeaderString("Access-Control-Allow-Origin"));
        assertNull(res.getHeaderString("Access-Control-Allow-Credentials"));
        assertNull(res.getHeaderString("Access-Control-Max-Age"));

        assertNull(res.getHeaderString("Access-Control-Allow-Headers"));
        assertNull(res.getHeaderString("Access-Control-Allow-Methods"));
    }

    @Test
    public void testOptionsPreflightInvalid2() {
        final String baseUri = getBaseUri().toString();
        final String origin = baseUri.substring(0, baseUri.length() - 1);
        final Response res = target(RESOURCE_PATH).request().header("Origin", origin)
                .header("Access-Control-Request-Method", "PUT")
                .header("Access-Control-Request-Headers", "Content-Type, Link, Bar").options();

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertNull(res.getHeaderString("Access-Control-Allow-Origin"));
        assertNull(res.getHeaderString("Access-Control-Allow-Credentials"));
        assertNull(res.getHeaderString("Access-Control-Max-Age"));

        assertNull(res.getHeaderString("Access-Control-Allow-Headers"));
        assertNull(res.getHeaderString("Access-Control-Allow-Methods"));
    }

    @Test
    public void testOptionsPreflightInvalid3() {
        final String baseUri = getBaseUri().toString();
        final String origin = baseUri.substring(0, baseUri.length() - 1);
        final Response res = target(RESOURCE_PATH).request().header("Origin", origin)
                .header("Access-Control-Request-Method", "FOO")
                .header("Access-Control-Request-Headers", "Content-Type, Link").options();

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertNull(res.getHeaderString("Access-Control-Allow-Origin"));
        assertNull(res.getHeaderString("Access-Control-Allow-Credentials"));
        assertNull(res.getHeaderString("Access-Control-Max-Age"));

        assertNull(res.getHeaderString("Access-Control-Allow-Headers"));
        assertNull(res.getHeaderString("Access-Control-Allow-Methods"));
    }

    @Test
    public void testOptionsPreflight() {
        final String baseUri = getBaseUri().toString();
        final String origin = baseUri.substring(0, baseUri.length() - 1);
        final Response res = target(RESOURCE_PATH).request().header("Origin", origin)
                .header("Access-Control-Request-Method", "PUT")
                .header("Access-Control-Request-Headers", "Content-Type, Link").options();

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertEquals(origin, res.getHeaderString("Access-Control-Allow-Origin"));
        assertEquals("true", res.getHeaderString("Access-Control-Allow-Credentials"));
        assertEquals("100", res.getHeaderString("Access-Control-Max-Age"));

        final List<String> headers = stream(res.getHeaderString("Access-Control-Allow-Headers").split(","))
                .collect(toList());
        assertEquals(3L, headers.size());
        assertTrue(headers.contains("link"));
        assertTrue(headers.contains("content-type"));
        assertTrue(headers.contains("accept-datetime"));

        final List<String> methods = stream(res.getHeaderString("Access-Control-Allow-Methods").split(","))
                .collect(toList());
        assertEquals(2L, methods.size());
        assertTrue(methods.contains("PUT"));
        assertTrue(methods.contains("PATCH"));
    }

    @Test
    public void testOptionsPreflightSimple() {
        final String baseUri = getBaseUri().toString();
        final String origin = baseUri.substring(0, baseUri.length() - 1);
        final Response res = target(RESOURCE_PATH).request().header("Origin", origin)
                .header("Access-Control-Request-Method", "POST").header("Access-Control-Request-Headers", "Accept")
                .options();

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertEquals(origin, res.getHeaderString("Access-Control-Allow-Origin"));
        assertEquals("true", res.getHeaderString("Access-Control-Allow-Credentials"));
        assertEquals("100", res.getHeaderString("Access-Control-Max-Age"));
        assertNull(res.getHeaderString("Access-Control-Allow-Methods"));
        assertNull(res.getHeaderString("Access-Control-Allow-Headers"));
    }

    @Test
    public void testOptionsNonexistent() {
        final Response res = target(NON_EXISTENT_PATH).request().options();

        assertEquals(NOT_FOUND, res.getStatusInfo());
    }

    @Test
    public void testOptionsGone() {
        final Response res = target(DELETED_PATH).request().options();

        assertEquals(GONE, res.getStatusInfo());
    }

    @Test
    public void testOptionsSlash() {
        final Response res = target(RESOURCE_PATH + "/").request().options();

        assertEquals(OK, res.getStatusInfo());

        assertTrue(res.getAllowedMethods().contains("PATCH"));
        assertTrue(res.getAllowedMethods().contains("PUT"));
        assertTrue(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        assertEquals(APPLICATION_SPARQL_UPDATE, res.getHeaderString(ACCEPT_PATCH));
        assertNull(res.getHeaderString(ACCEPT_POST));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testOptionsTimemap() {
        when(mockResource.getMementos()).thenReturn(
                asList(new VersionRange(ofEpochSecond(timestamp - 2000), ofEpochSecond(timestamp - 1000)),
                        new VersionRange(ofEpochSecond(timestamp - 1000), time),
                        new VersionRange(time, ofEpochSecond(timestamp + 1000))));

        final Response res = target(RESOURCE_PATH).queryParam("ext", "timemap").request().options();

        assertEquals(NO_CONTENT, res.getStatusInfo());

        assertFalse(res.getAllowedMethods().contains("PATCH"));
        assertFalse(res.getAllowedMethods().contains("PUT"));
        assertFalse(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        assertNull(res.getHeaderString(ACCEPT_PATCH));
        assertNull(res.getHeaderString(ACCEPT_POST));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testOptionsVersion() {
        final Response res = target(RESOURCE_PATH).queryParam("version", timestamp).request().options();

        assertEquals(NO_CONTENT, res.getStatusInfo());

        assertFalse(res.getAllowedMethods().contains("PATCH"));
        assertFalse(res.getAllowedMethods().contains("PUT"));
        assertFalse(res.getAllowedMethods().contains("DELETE"));
        assertTrue(res.getAllowedMethods().contains("GET"));
        assertTrue(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertFalse(res.getAllowedMethods().contains("POST"));

        assertNull(res.getHeaderString(ACCEPT_PATCH));
        assertNull(res.getHeaderString(ACCEPT_POST));
    }

    /* ******************************* *
     *            POST Tests
     * ******************************* */
    @Test
    public void testPost() {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH + "/" + RANDOM_VALUE)),
                eq(MAX))).thenReturn(empty());

        final Response res = target(RESOURCE_PATH).request()
                .post(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(CREATED, res.getStatusInfo());
        assertEquals(BASE_URL + RESOURCE_PATH + "/" + RANDOM_VALUE, res.getLocation().toString());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
    }

    @Test
    public void testPostInvalidLink() {
        final Response res = target(RESOURCE_PATH).request().header("Link", "I never really liked his friends")
                .post(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(BAD_REQUEST, res.getStatusInfo());
    }

    @Test
    public void testPostToTimemap() {
        final Response res = target(RESOURCE_PATH).queryParam("ext", "timemap").request()
                .post(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(METHOD_NOT_ALLOWED, res.getStatusInfo());
    }

    @Test
    public void testPostTypeMismatch() {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH + "/" + RANDOM_VALUE)),
                eq(MAX))).thenReturn(empty());

        final Response res = target(RESOURCE_PATH).request()
                .header("Link", "<http://www.w3.org/ns/ldp#NonRDFSource>; rel=\"type\"")
                .post(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(BAD_REQUEST, res.getStatusInfo());
    }

    @Test
    public void testPostUnknownLinkType() {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH + "/" + RANDOM_VALUE)),
                eq(MAX))).thenReturn(empty());

        final Response res = target(RESOURCE_PATH).request()
                .header("Link", "<http://example.com/types/Foo>; rel=\"type\"")
                .post(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(CREATED, res.getStatusInfo());
        assertEquals(BASE_URL + RESOURCE_PATH + "/" + RANDOM_VALUE, res.getLocation().toString());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
    }

    @Test
    public void testPostBadContent() {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH + "/" + RANDOM_VALUE)),
                eq(MAX))).thenReturn(empty());

        final Response res = target(RESOURCE_PATH).request()
                .post(entity("<> <http://purl.org/dc/terms/title> A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(BAD_REQUEST, res.getStatusInfo());
    }

    @Test
    public void testPostToLdpRs() {
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH + "/" + RANDOM_VALUE)),
                eq(MAX))).thenReturn(empty());

        final Response res = target(RESOURCE_PATH).request()
                .post(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(METHOD_NOT_ALLOWED, res.getStatusInfo());
    }

    @Test
    public void testPostSlug() {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);

        final Response res = target(RESOURCE_PATH).request().header("Slug", "child")
                .post(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(CREATED, res.getStatusInfo());
        assertEquals(BASE_URL + CHILD_PATH, res.getLocation().toString());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
    }

    @Test
    public void testPostBadSlug() {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);

        final Response res = target(RESOURCE_PATH).request().header("Slug", "child/grandchild")
                .post(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(BAD_REQUEST, res.getStatusInfo());
    }

    @Test
    public void testPostVersion() {
        final Response res = target(RESOURCE_PATH).queryParam("version", timestamp).request().header("Slug", "test")
                .post(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(METHOD_NOT_ALLOWED, res.getStatusInfo());
    }

    @Test
    public void testPostAcl() {
        final Response res = target(RESOURCE_PATH).queryParam("ext", "acl").request().header("Slug", "test")
                .post(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(METHOD_NOT_ALLOWED, res.getStatusInfo());
    }

    @Test
    public void testPostConstraint() {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH + "/" + RANDOM_VALUE)),
                eq(MAX))).thenReturn(empty());

        final Response res = target(RESOURCE_PATH).request()
                .post(entity("<> <http://www.w3.org/ns/ldp#inbox> \"Some literal\" .", TEXT_TURTLE_TYPE));

        assertEquals(CONFLICT, res.getStatusInfo());
        assertTrue(
                res.getLinks().stream().anyMatch(hasLink(Trellis.InvalidRange, LDP.constrainedBy.getIRIString())));
    }

    @Test
    public void testPostNonexistent() {
        final Response res = target(NON_EXISTENT_PATH).request()
                .post(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(NOT_FOUND, res.getStatusInfo());
    }

    @Test
    public void testPostGone() {
        final Response res = target(DELETED_PATH).request()
                .post(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(GONE, res.getStatusInfo());
    }

    @Test
    public void testPostBinary() {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH + "/newresource")),
                any(Instant.class))).thenReturn(empty());
        final Response res = target(RESOURCE_PATH).request().header("Slug", "newresource")
                .post(entity("some data.", TEXT_PLAIN_TYPE));

        assertEquals(CREATED, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
    }

    @Test
    public void testPostBinaryWithInvalidDigest() {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH + "/newresource")),
                any(Instant.class))).thenReturn(empty());
        final Response res = target(RESOURCE_PATH).request().header("Slug", "newresource")
                .header("Digest", "md5=blahblah").post(entity("some data.", TEXT_PLAIN_TYPE));

        assertEquals(BAD_REQUEST, res.getStatusInfo());
    }

    @Test
    public void testPostUnparseableDigest() {
        final Response res = target(RESOURCE_PATH).request().header("Digest", "digest this, man!")
                .post(entity("some data.", TEXT_PLAIN_TYPE));

        assertEquals(BAD_REQUEST, res.getStatusInfo());
    }

    @Test
    public void testPostBinaryWithInvalidDigestType() {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH + "/newresource")),
                any(Instant.class))).thenReturn(empty());
        final Response res = target(RESOURCE_PATH).request().header("Slug", "newresource")
                .header("Digest", "uh=huh").post(entity("some data.", TEXT_PLAIN_TYPE));

        assertEquals(BAD_REQUEST, res.getStatusInfo());
    }

    @Test
    public void testPostBinaryWithMd5Digest() {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH + "/newresource")),
                any(Instant.class))).thenReturn(empty());
        final Response res = target(RESOURCE_PATH).request().header("Digest", "md5=BJozgIQwPzzVzSxvjQsWkA==")
                .header("Slug", "newresource").post(entity("some data.", TEXT_PLAIN_TYPE));

        assertEquals(CREATED, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
    }

    @Test
    public void testPostBinaryWithSha1Digest() {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH + "/newresource")),
                any(Instant.class))).thenReturn(empty());
        final Response res = target(RESOURCE_PATH).request().header("Digest", "sha=3VWEuvPnAM6riDQJUu4TG7A4Ots=")
                .header("Slug", "newresource").post(entity("some data.", TEXT_PLAIN_TYPE));

        assertEquals(CREATED, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
    }

    @Test
    public void testPostBinaryWithSha256Digest() {
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH + "/newresource")),
                any(Instant.class))).thenReturn(empty());
        final Response res = target(RESOURCE_PATH).request()
                .header("Digest", "sha-256=voCCIRTNXosNlEgQ/7IuX5dFNvFQx5MfG/jy1AKiLMU=")
                .header("Slug", "newresource").post(entity("some data.", TEXT_PLAIN_TYPE));

        assertEquals(CREATED, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
    }

    @Test
    public void testPostSlash() {
        final Response res = target(RESOURCE_PATH + "/").request().header("Slug", "test")
                .post(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(OK, res.getStatusInfo());
    }

    /* ******************************* *
     *            PUT Tests
     * ******************************* */
    @Test
    public void testPutExisting() {
        final Response res = target(RESOURCE_PATH).request()
                .put(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testPutExistingUnknownLink() {
        final Response res = target(RESOURCE_PATH).request()
                .header("Link", "<http://example.com/types/Foo>; rel=\"type\"")
                .put(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testPutExistingSubclassLink() {
        final Response res = target(RESOURCE_PATH).request().header("Link", LDP.Container + "; rel=\"type\"")
                .put(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testPutExistingMalformed() {
        final Response res = target(RESOURCE_PATH).request()
                .put(entity("<> <http://purl.org/dc/terms/title \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(BAD_REQUEST, res.getStatusInfo());
    }

    @Test
    public void testPutConstraint() {
        final Response res = target(RESOURCE_PATH).request().put(entity(
                "<> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> \"Some literal\" .", TEXT_TURTLE_TYPE));

        assertEquals(CONFLICT, res.getStatusInfo());
        assertTrue(
                res.getLinks().stream().anyMatch(hasLink(Trellis.InvalidRange, LDP.constrainedBy.getIRIString())));
    }

    @Test
    public void testPutNew() {
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH + "/test")), eq(MAX)))
                .thenReturn(empty());

        final Response res = target(RESOURCE_PATH + "/test").request()
                .put(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testPutDeleted() {
        final Response res = target(DELETED_PATH).request()
                .put(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testPutVersion() {
        final Response res = target(RESOURCE_PATH).queryParam("version", timestamp).request()
                .put(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(METHOD_NOT_ALLOWED, res.getStatusInfo());
    }

    @Test
    public void testPutAcl() {
        final Response res = target(RESOURCE_PATH).queryParam("ext", "acl").request()
                .put(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
    }

    @Test
    public void testPutUploads() {
        final Response res = target(RESOURCE_PATH).queryParam("ext", UPLOADS).request()
                .put(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(METHOD_NOT_ALLOWED, res.getStatusInfo());
    }

    @Test
    public void testPutBinary() {
        final Response res = target(BINARY_PATH).request().put(entity("some data.", TEXT_PLAIN_TYPE));

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
    }

    @Test
    public void testPutBinaryWithInvalidDigest() {
        final Response res = target(BINARY_PATH).request().header("Digest", "md5=blahblah")
                .put(entity("some data.", TEXT_PLAIN_TYPE));

        assertEquals(BAD_REQUEST, res.getStatusInfo());
    }

    @Test
    public void testPutBinaryWithMd5Digest() {
        final Response res = target(BINARY_PATH).request().header("Digest", "md5=BJozgIQwPzzVzSxvjQsWkA==")
                .put(entity("some data.", TEXT_PLAIN_TYPE));

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
    }

    @Test
    public void testPutBinaryWithSha1Digest() {
        final Response res = target(BINARY_PATH).request().header("Digest", "sha=3VWEuvPnAM6riDQJUu4TG7A4Ots=")
                .put(entity("some data.", TEXT_PLAIN_TYPE));

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
    }

    @Test
    public void testPutBinaryToACL() {
        final Response res = target(BINARY_PATH).queryParam("ext", "acl").request()
                .put(entity("some data.", TEXT_PLAIN_TYPE));

        assertEquals(NOT_ACCEPTABLE, res.getStatusInfo());
    }

    @Test
    public void testPutIfMatch() {
        final Response res = target(BINARY_PATH).request()
                .header("If-Match", "\"0ccfe1dcf1d081e016ceaada4c6f00ef\"")
                .put(entity("some different data.", TEXT_PLAIN_TYPE));

        assertEquals(NO_CONTENT, res.getStatusInfo());
    }

    @Test
    public void testPutBadIfMatch() {
        final Response res = target(BINARY_PATH).request().header("If-Match", "0ccfe1dcf1d081e016ceaada4c6f00ef")
                .put(entity("some different data.", TEXT_PLAIN_TYPE));

        assertEquals(BAD_REQUEST, res.getStatusInfo());
    }

    @Test
    public void testPutIfUnmodified() {
        final Response res = target(BINARY_PATH).request()
                .header("If-Unmodified-Since", "Tue, 29 Aug 2017 07:14:52 GMT")
                .put(entity("some different data.", TEXT_PLAIN_TYPE));

        assertEquals(NO_CONTENT, res.getStatusInfo());
    }

    @Test
    public void testPutPreconditionFailed() {
        final Response res = target(BINARY_PATH).request().header("If-Match", "\"blahblahblah\"")
                .put(entity("some different data.", TEXT_PLAIN_TYPE));

        assertEquals(PRECONDITION_FAILED, res.getStatusInfo());
    }

    @Test
    public void testPutPreconditionFailed2() {
        final Response res = target(BINARY_PATH).request()
                .header("If-Unmodified-Since", "Wed, 19 Oct 2016 10:15:00 GMT")
                .put(entity("some different data.", TEXT_PLAIN_TYPE));

        assertEquals(PRECONDITION_FAILED, res.getStatusInfo());
    }

    @Test
    public void testPutBinaryWithSha256Digest() {
        final Response res = target(BINARY_PATH).request()
                .header("Digest", "sha-256=voCCIRTNXosNlEgQ/7IuX5dFNvFQx5MfG/jy1AKiLMU=")
                .put(entity("some data.", TEXT_PLAIN_TYPE));

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.NonRDFSource)));
    }

    @Test
    public void testPutSlash() {
        final Response res = target(RESOURCE_PATH + "/").request()
                .put(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(OK, res.getStatusInfo());
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testInvalidPartitionPut() {
        final Response res = target("/foo/bar").request()
                .put(entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(NOT_FOUND, res.getStatusInfo());
    }

    /* ******************************* *
     *            DELETE Tests
     * ******************************* */
    @Test
    public void testDeleteExisting() {
        final Response res = target(RESOURCE_PATH).request().delete();

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testDeleteNonexistent() {
        final Response res = target(NON_EXISTENT_PATH).request().delete();

        assertEquals(NOT_FOUND, res.getStatusInfo());
    }

    @Test
    public void testDeleteDeleted() {
        final Response res = target(DELETED_PATH).request().delete();

        assertEquals(GONE, res.getStatusInfo());
    }

    @Test
    public void testDeleteVersion() {
        final Response res = target(RESOURCE_PATH).queryParam("version", timestamp).request().delete();

        assertEquals(METHOD_NOT_ALLOWED, res.getStatusInfo());
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testDeleteNonExistant() {
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH + "/test")), eq(MAX)))
                .thenReturn(empty());

        final Response res = target(RESOURCE_PATH + "/test").request().delete();

        assertEquals(NOT_FOUND, res.getStatusInfo());
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testDeleteWithChildren() {
        when(mockVersionedResource.getInteractionModel()).thenReturn(LDP.Container);
        when(mockVersionedResource.stream(eq(LDP.PreferContainment))).thenAnswer(inv -> Stream.of(
                rdf.createTriple(identifier, LDP.contains, rdf.createIRI(identifier.getIRIString() + "/child"))));

        final Response res = target(RESOURCE_PATH).request().delete();

        assertEquals(NO_CONTENT, res.getStatusInfo());
    }

    @Test
    public void testDeleteNoChildren1() {
        when(mockVersionedResource.getInteractionModel()).thenReturn(LDP.BasicContainer);
        when(mockVersionedResource.stream(eq(LDP.PreferContainment))).thenAnswer(inv -> Stream.empty());

        final Response res = target(RESOURCE_PATH).request().delete();

        assertEquals(NO_CONTENT, res.getStatusInfo());
    }

    @Test
    public void testDeleteNoChildren2() {
        when(mockVersionedResource.getInteractionModel()).thenReturn(LDP.Container);
        when(mockVersionedResource.stream(eq(LDP.PreferContainment))).thenAnswer(inv -> Stream.empty());

        final Response res = target(RESOURCE_PATH).request().delete();

        assertEquals(NO_CONTENT, res.getStatusInfo());
    }

    @Test
    public void testDeleteUploads() {
        final Response res = target(RESOURCE_PATH).queryParam("ext", UPLOADS).request().delete();

        assertEquals(METHOD_NOT_ALLOWED, res.getStatusInfo());
    }

    @Test
    public void testDeleteAcl() {
        final Response res = target(RESOURCE_PATH).queryParam("ext", "acl").request().delete();

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testDeleteSlash() {
        final Response res = target(RESOURCE_PATH + "/").request().delete();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    /* ********************* *
     *      PATCH tests
     * ********************* */
    @Test
    public void testPatchVersion() {
        final Response res = target(RESOURCE_PATH).queryParam("version", timestamp).request().method("PATCH",
                entity("INSERT { <> <http://purl.org/dc/terms/title> \"A title\" } WHERE {}",
                        APPLICATION_SPARQL_UPDATE));

        assertEquals(METHOD_NOT_ALLOWED, res.getStatusInfo());
    }

    @Test
    public void testPatchExisting() {
        final Response res = target(RESOURCE_PATH).request().method("PATCH", entity(
                "INSERT { <> <http://purl.org/dc/terms/title> \"A title\" } WHERE {}", APPLICATION_SPARQL_UPDATE));

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testPatchExistingResponse() throws IOException {
        final Response res = target(RESOURCE_PATH).request()
                .header("Prefer",
                        "return=representation; include=\"" + Trellis.PreferUserManaged.getIRIString() + "\"")
                .method("PATCH", entity("INSERT { <> <http://purl.org/dc/terms/title> \"A title\" } WHERE {}",
                        APPLICATION_SPARQL_UPDATE));

        assertEquals(OK, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        assertTrue(entity.contains("A title"));
    }

    @Test
    public void testPatchConstraint() {
        final Response res = target(RESOURCE_PATH).request().method("PATCH",
                entity("INSERT { <> a \"Some literal\" } WHERE {}", APPLICATION_SPARQL_UPDATE));

        assertEquals(CONFLICT, res.getStatusInfo());
        assertTrue(
                res.getLinks().stream().anyMatch(hasLink(Trellis.InvalidRange, LDP.constrainedBy.getIRIString())));
    }

    @Test
    public void testPatchToTimemap() {
        final Response res = target(RESOURCE_PATH).queryParam("ext", "timemap").request().method("PATCH",
                entity("<> <http://purl.org/dc/terms/title> \"A title\" .", TEXT_TURTLE_TYPE));

        assertEquals(METHOD_NOT_ALLOWED, res.getStatusInfo());
    }

    @Test
    public void testPatchNew() {
        when(mockResourceService.get(eq(rdf.createIRI(TRELLIS_PREFIX + RESOURCE_PATH + "/test")), eq(MAX)))
                .thenReturn(empty());

        final Response res = target(RESOURCE_PATH + "/test").request().method("PATCH", entity(
                "INSERT { <> <http://purl.org/dc/terms/title> \"A title\" } WHERE {}", APPLICATION_SPARQL_UPDATE));

        assertEquals(NOT_FOUND, res.getStatusInfo());
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testPatchAcl() {
        final Response res = target(RESOURCE_PATH).queryParam("ext", "acl").request().method("PATCH", entity(
                "INSERT { <> <http://purl.org/dc/terms/title> \"A title\" } WHERE {}", APPLICATION_SPARQL_UPDATE));

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.Resource)));
        assertTrue(res.getLinks().stream().anyMatch(hasType(LDP.RDFSource)));
        assertFalse(res.getLinks().stream().anyMatch(hasType(LDP.Container)));
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testPatchInvalidContent() {
        final Response res = target(RESOURCE_PATH).request().method("PATCH",
                entity("blah blah blah", "invalid/type"));

        assertEquals(UNSUPPORTED_MEDIA_TYPE, res.getStatusInfo());
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testPatchSlash() {
        final Response res = target(RESOURCE_PATH + "/").request().method("PATCH", entity(
                "INSERT { <> <http://purl.org/dc/terms/title> \"A title\" } WHERE {}", APPLICATION_SPARQL_UPDATE));

        assertEquals(OK, res.getStatusInfo());
        assertNull(res.getHeaderString(MEMENTO_DATETIME));
    }

    @Test
    public void testPatchUpload() {
        final Response res = target(RESOURCE_PATH).queryParam("ext", UPLOADS).request().method("PATCH", entity(
                "INSERT { <> <http://purl.org/dc/terms/title> \"A title\" } WHERE {}", APPLICATION_SPARQL_UPDATE));

        assertEquals(METHOD_NOT_ALLOWED, res.getStatusInfo());
    }

    /**
     * Some other method
     */
    @Test
    public void testOtherMethod() {
        final Response res = target(RESOURCE_PATH).request().method("FOO");
        assertEquals(METHOD_NOT_ALLOWED, res.getStatusInfo());
    }

    /**
     * An other partition
     */
    @Test
    public void testOtherParition() {
        final Response res = target("other/object").request().get();
        assertEquals(NOT_FOUND, res.getStatusInfo());
    }

    /**
     * Multipart upload tests
     */
    @Test
    public void testMultipartExtLinkHeaderLDPRS() {
        when(mockBinaryResolver.supportsMultipartUpload()).thenReturn(true);
        final Response res = target(RESOURCE_PATH).request().get();

        assertEquals(OK, res.getStatusInfo());
        assertFalse(res.getLinks().stream()
                .anyMatch(hasLink(rdf.createIRI(BASE_URL + RESOURCE_PATH + "?ext=" + UPLOADS),
                        Trellis.multipartUploadService.getIRIString())));
    }

    @Test
    public void testMultipartOptions() {
        final Response res = target(BINARY_PATH).queryParam("ext", UPLOADS).request().options();

        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertFalse(res.getAllowedMethods().contains("PATCH"));
        assertFalse(res.getAllowedMethods().contains("PUT"));
        assertFalse(res.getAllowedMethods().contains("DELETE"));
        assertFalse(res.getAllowedMethods().contains("GET"));
        assertFalse(res.getAllowedMethods().contains("HEAD"));
        assertTrue(res.getAllowedMethods().contains("OPTIONS"));
        assertTrue(res.getAllowedMethods().contains("POST"));
    }

    @Test
    public void testMultipartStart() {
        when(mockBinaryResolver.supportsMultipartUpload()).thenReturn(true);
        when(mockBinaryResolver.initiateUpload(eq(REPO1), any(), any())).thenReturn(RANDOM_VALUE);
        final Response res = target(BINARY_PATH).queryParam("ext", UPLOADS).request()
                .post(entity("", TEXT_PLAIN_TYPE));

        assertEquals(CREATED, res.getStatusInfo());
        assertEquals(BASE_URL + UPLOAD_PREFIX + REPO1 + "/" + RANDOM_VALUE, res.getLocation().toString());
    }

    @Test
    public void testMultipartStartError() {
        when(mockBinaryResolver.supportsMultipartUpload()).thenReturn(true);
        final Response res = target(BINARY_PATH).queryParam("ext", UPLOADS).request()
                .post(entity("", TEXT_PLAIN_TYPE));

        assertEquals(BAD_REQUEST, res.getStatusInfo());
    }

    @Test
    public void testMultipartStartNotAllowed() {
        final Response res = target(BINARY_PATH).queryParam("ext", UPLOADS).request()
                .post(entity("", TEXT_PLAIN_TYPE));

        assertEquals(METHOD_NOT_ALLOWED, res.getStatusInfo());
    }

    @Test
    public void testMultipartExtLinkHeaderContainer() {
        when(mockBinaryResolver.supportsMultipartUpload()).thenReturn(true);
        when(mockResource.getInteractionModel()).thenReturn(LDP.Container);
        final Response res = target(RESOURCE_PATH).request().get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(res.getLinks().stream()
                .anyMatch(hasLink(rdf.createIRI(BASE_URL + RESOURCE_PATH + "?ext=" + UPLOADS),
                        Trellis.multipartUploadService.getIRIString())));
    }

    @Test
    public void testMultipartExtLinkHeader() {
        when(mockBinaryResolver.supportsMultipartUpload()).thenReturn(true);
        final Response res = target(BINARY_PATH).request().get();

        assertEquals(OK, res.getStatusInfo());
        assertTrue(
                res.getLinks().stream().anyMatch(hasLink(rdf.createIRI(BASE_URL + BINARY_PATH + "?ext=" + UPLOADS),
                        Trellis.multipartUploadService.getIRIString())));
    }

    @Test
    public void testMultipartGet() {
        when(mockBinaryResolver.supportsMultipartUpload()).thenReturn(true);
        when(mockBinaryResolver.uploadSessionExists(eq(UPLOAD_SESSION_ID))).thenReturn(true);
        when(mockBinaryResolver.listParts(eq(UPLOAD_SESSION_ID)))
                .thenAnswer(x -> Stream.of(new SimpleEntry<>(1, "digest1"), new SimpleEntry<>(2, "digest2")));
        final Response res = target("upload/" + REPO1 + "/" + UPLOAD_SESSION_ID).request().get();
        assertEquals(OK, res.getStatusInfo());
    }

    @Test
    public void testMultipartGetNotFound() {
        when(mockBinaryResolver.supportsMultipartUpload()).thenReturn(true);

        final Response res = target("upload/" + REPO1 + "/" + UPLOAD_SESSION_ID).request().get();
        assertEquals(NOT_FOUND, res.getStatusInfo());
    }

    @Test
    public void testMultipartPut() throws IOException {
        final InputStream inputStream = new ByteArrayInputStream("blah blah blah".getBytes(UTF_8));
        when(mockBinaryResolver.supportsMultipartUpload()).thenReturn(true);
        when(mockBinaryResolver.uploadSessionExists(eq(UPLOAD_SESSION_ID))).thenReturn(true);
        when(mockBinaryResolver.uploadPart(eq(UPLOAD_SESSION_ID), eq(15), any(InputStream.class)))
                .thenReturn("digest1");

        final Response res = target("upload/" + REPO1 + "/" + UPLOAD_SESSION_ID + "/15").request()
                .put(entity("blah blah blah", TEXT_PLAIN_TYPE));
        assertEquals(OK, res.getStatusInfo());
        final String entity = IOUtils.toString((InputStream) res.getEntity(), UTF_8);
        assertEquals("{\"digest\":\"digest1\"}", entity);
    }

    @Test
    public void testMultipartPutNotFound() throws IOException {
        when(mockBinaryResolver.supportsMultipartUpload()).thenReturn(true);

        final Response res = target("upload/" + REPO1 + "/" + UPLOAD_SESSION_ID + "/15").request()
                .put(entity("blah blah blah", TEXT_PLAIN_TYPE));
        assertEquals(NOT_FOUND, res.getStatusInfo());
    }

    @Test
    public void testMultipartPost() {
        final BinaryService.MultipartUpload upload = new BinaryService.MultipartUpload(BASE_URL, BINARY_PATH,
                new HttpSession(), mockBinary);

        when(mockBinaryResolver.supportsMultipartUpload()).thenReturn(true);
        when(mockBinaryResolver.uploadSessionExists(eq(UPLOAD_SESSION_ID))).thenReturn(true);
        when(mockBinaryResolver.completeUpload(eq(UPLOAD_SESSION_ID), any())).thenReturn(upload);

        final Response res = target("upload/" + REPO1 + "/" + UPLOAD_SESSION_ID).request()
                .post(entity("{\"20\": \"value\"}", APPLICATION_JSON_TYPE));
        assertEquals(CREATED, res.getStatusInfo());
    }

    @Test
    public void testMultipartPostError() {
        when(mockResourceService.put(any(IRI.class), any(Dataset.class))).thenReturn(false);
        final BinaryService.MultipartUpload upload = new BinaryService.MultipartUpload(BASE_URL, BINARY_PATH,
                new HttpSession(), mockBinary);

        when(mockBinaryResolver.supportsMultipartUpload()).thenReturn(true);
        when(mockBinaryResolver.uploadSessionExists(eq(UPLOAD_SESSION_ID))).thenReturn(true);
        when(mockBinaryResolver.completeUpload(eq(UPLOAD_SESSION_ID), any())).thenReturn(upload);

        final Response res = target("upload/" + REPO1 + "/" + UPLOAD_SESSION_ID).request()
                .post(entity("{\"20\": \"value\"}", APPLICATION_JSON_TYPE));
        assertEquals(INTERNAL_SERVER_ERROR, res.getStatusInfo());
    }

    @Test
    public void testMultipartDelete() {
        when(mockBinaryResolver.supportsMultipartUpload()).thenReturn(true);
        when(mockBinaryResolver.uploadSessionExists(eq(UPLOAD_SESSION_ID))).thenReturn(true);

        final Response res = target("upload/" + REPO1 + "/" + UPLOAD_SESSION_ID).request().delete();
        assertEquals(NO_CONTENT, res.getStatusInfo());
    }

    @Test
    public void testMultipartDeleteNotFound() {
        when(mockBinaryResolver.supportsMultipartUpload()).thenReturn(true);
        when(mockBinaryResolver.uploadSessionExists(eq(UPLOAD_SESSION_ID))).thenReturn(false);

        final Response res = target("upload/" + REPO1 + "/" + UPLOAD_SESSION_ID).request().delete();
        assertEquals(NOT_FOUND, res.getStatusInfo());
    }

    /* ************************************ *
     *      Test cache control headers
     * ************************************ */
    @Test
    public void testCacheControl() {
        final Response res = target(RESOURCE_PATH).request().get();
        assertEquals(OK, res.getStatusInfo());
        assertNotNull(res.getHeaderString(CACHE_CONTROL));
        assertTrue(res.getHeaderString(CACHE_CONTROL).contains("max-age="));
    }

    @Test
    public void testCacheControlOptions() {
        final Response res = target(RESOURCE_PATH).request().options();
        assertEquals(NO_CONTENT, res.getStatusInfo());
        assertNull(res.getHeaderString(CACHE_CONTROL));
    }

    protected static Predicate<Link> hasLink(final IRI iri, final String rel) {
        return link -> rel.equals(link.getRel()) && iri.getIRIString().equals(link.getUri().toString());
    }

    protected static Predicate<Link> hasType(final IRI iri) {
        return hasLink(iri, "type");
    }
}