/*
 * Copyright (c) 2020 Aaron Coburn and individual contributors
 *
 * 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.jena;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.stream.Stream.of;
import static org.apache.commons.rdf.api.RDFSyntax.JSONLD;
import static org.apache.commons.rdf.api.RDFSyntax.NTRIPLES;
import static org.apache.commons.rdf.api.RDFSyntax.RDFA;
import static org.apache.commons.rdf.api.RDFSyntax.RDFXML;
import static org.apache.commons.rdf.api.RDFSyntax.TURTLE;
import static org.apache.jena.commonsrdf.JenaCommonsRDF.fromJena;
import static org.apache.jena.graph.Factory.createDefaultGraph;
import static org.apache.jena.graph.NodeFactory.createBlankNode;
import static org.apache.jena.graph.NodeFactory.createLiteral;
import static org.apache.jena.graph.NodeFactory.createURI;
import static org.apache.jena.graph.Triple.create;
import static org.apache.jena.vocabulary.DCTerms.spatial;
import static org.apache.jena.vocabulary.DCTerms.subject;
import static org.apache.jena.vocabulary.DCTerms.title;
import static org.apache.jena.vocabulary.DCTypes.Text;
import static org.apache.jena.vocabulary.RDF.Nodes.type;
import static org.apache.jena.vocabulary.RDF.uri;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.trellisldp.api.Syntax.LD_PATCH;
import static org.trellisldp.api.Syntax.SPARQL_UPDATE;
import static org.trellisldp.vocabulary.JSONLD.compacted;
import static org.trellisldp.vocabulary.JSONLD.expanded;
import static org.trellisldp.vocabulary.JSONLD.flattened;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;

import org.apache.commons.rdf.api.Graph;
import org.apache.commons.rdf.api.RDF;
import org.apache.commons.rdf.api.RDFSyntax;
import org.apache.commons.rdf.api.RDFTerm;
import org.apache.commons.rdf.api.Triple;
import org.apache.jena.commonsrdf.JenaCommonsRDF;
import org.apache.jena.graph.Node;
import org.apache.jena.riot.Lang;
import org.apache.jena.riot.RDFDataMgr;
import org.apache.jena.vocabulary.DCTerms;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import org.mockito.Mock;
import org.trellisldp.api.CacheService;
import org.trellisldp.api.IOService;
import org.trellisldp.api.NamespaceService;
import org.trellisldp.api.RDFFactory;
import org.trellisldp.api.RDFaWriterService;
import org.trellisldp.api.TrellisRuntimeException;
import org.trellisldp.vocabulary.Trellis;

/**
 * @author acoburn
 */
class JenaIOServiceTest {

    private static final RDF rdf = RDFFactory.getInstance();
    private static final String identifier = "http://example.com/resource";

    private IOService service, service2, service3;

    @Mock
    private NamespaceService mockNamespaceService;

    @Mock
    private InputStream mockInputStream;

    @Mock
    private OutputStream mockOutputStream;

    @Mock
    private RDFaWriterService mockRdfaWriterService;

    @Mock
    private CacheService<String, String> mockCache;

    @Mock
    private RDFSyntax mockSyntax;

    @BeforeEach
    @SuppressWarnings("unchecked")
    void setUp() {
        initMocks(this);
        final Map<String, String> namespaces = new HashMap<>();
        namespaces.put("dcterms", DCTerms.NS);
        namespaces.put("rdf", uri);

        service = new JenaIOService(mockNamespaceService, null, mockCache,
                "http://www.w3.org/ns/anno.jsonld,,,", "http://www.trellisldp.org/ns/", false);

        service2 = new JenaIOService(mockNamespaceService, mockRdfaWriterService, mockCache, emptySet(),
                singleton("http://www.w3.org/ns/"), false);

        service3 = new JenaIOService(mockNamespaceService, null, mockCache, emptySet(), emptySet(), true);

        when(mockNamespaceService.getNamespaces()).thenReturn(namespaces);
        when(mockCache.get(anyString(), any(Function.class))).thenAnswer(inv -> {
            final String key = inv.getArgument(0);
            final Function<String, String> mapper = inv.getArgument(1);
            return mapper.apply(key);
        });
    }

    @Test
    void testJsonLdDefaultSerializer() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service3.write(getTriples(), out, JSONLD, identifier);
        final String output = out.toString("UTF-8");
        final Graph graph = rdf.createGraph();
        service3.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, null).forEach(graph::add);
        assertAll("Check compact serialization", checkCompactSerialization(output, graph));
    }

    @Test
    void testJsonLdExpandedSerializer() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service.write(getTriples(), out, JSONLD, identifier, expanded);
        final String output = out.toString("UTF-8");
        final Graph graph = rdf.createGraph();
        service.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, null).forEach(graph::add);
        assertAll("Check expanded serialization", checkExpandedSerialization(output, graph));
    }

    @Test
    void testJsonLdExpandedFlatSerializer() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service.write(getTriples(), out, JSONLD, identifier, expanded, flattened);
        final String output = out.toString("UTF-8");
        final Graph graph = rdf.createGraph();
        service.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, null).forEach(graph::add);
        assertAll("Check expanded serialization", checkExpandedSerialization(output, graph));
    }

    @Test
    void testJsonLdFlatExpandedSerializer() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service.write(getTriples(), out, JSONLD, identifier, flattened, expanded);
        final String output = out.toString("UTF-8");
        final Graph graph = rdf.createGraph();
        service.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, null).forEach(graph::add);
        assertAll("Check expanded serialization", checkFlattenedSerialization(output, graph));
    }

    @Test
    void testJsonLdCustomSerializer() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service.write(getTriples(), out, JSONLD, identifier, rdf.createIRI("http://www.w3.org/ns/anno.jsonld"));
        final String output = out.toString("UTF-8");
        assertTrue(output.contains("\"dcterms:title\":\"A title\""), "missing dcterms:title!");
        assertTrue(output.contains("\"type\":\"Text\""), "missing rdf:type Text!");
        assertTrue(output.contains("\"@context\":"), "missing @context!");
        assertTrue(output.contains("\"@context\":\"http://www.w3.org/ns/anno.jsonld\""), "Incorrect @context value!");
        assertFalse(output.contains("\"@graph\":"), "unexpected @graph!");

        final Graph graph = rdf.createGraph();
        service.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, identifier).forEach(graph::add);
        assertTrue(validateGraph(graph), "Not all triples present in output graph!");
    }

    @Test
    void testJsonLdCustomSerializerNoopCache() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        final IOService svc = new JenaIOService(mockNamespaceService, null, new NoopProfileCache(),
                "http://www.w3.org/ns/anno.jsonld,,,", "http://www.trellisldp.org/ns/", true);

        svc.write(getTriples(), out, JSONLD, identifier, rdf.createIRI("http://www.w3.org/ns/anno.jsonld"));
        final String output = out.toString("UTF-8");
        final Graph graph = rdf.createGraph();
        service.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, identifier).forEach(graph::add);
        assertTrue(validateGraph(graph), "Not all triples present in output graph!");
    }


    @Test
    void testJsonLdCustomSerializer2() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service2.write(getTriples(), out, JSONLD, identifier, rdf.createIRI("http://www.w3.org/ns/anno.jsonld"));
        final String output = out.toString("UTF-8");
        assertTrue(output.contains("\"dcterms:title\":\"A title\""), "missing/incorrect dcterms:title!");
        assertTrue(output.contains("\"type\":\"Text\""), "missing/incorrect rdf:type!");
        assertTrue(output.contains("\"@context\":"), "missing @context!");
        assertTrue(output.contains("\"@context\":\"http://www.w3.org/ns/anno.jsonld\""), "Incorrect @context value!");
        assertFalse(output.contains("\"@graph\":"), "unexpected @graph!");

        final Graph graph = rdf.createGraph();
        service2.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, null).forEach(graph::add);
        assertTrue(validateGraph(graph), "Not all triples present in output graph!");
    }

    @Test
    void testJsonLdCustomUnrecognizedSerializer() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service.write(getTriples(), out, JSONLD, identifier, rdf.createIRI("http://www.example.org/context.jsonld"));
        final String output = out.toString("UTF-8");
        final Graph graph = rdf.createGraph();
        service.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, null).forEach(graph::add);
        assertAll("Check compact serialization", checkCompactSerialization(output, graph));
    }

    @Test
    void testJsonLdNullCache() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        final IOService myservice = new JenaIOService();
        myservice.write(getTriples(), out, JSONLD, identifier, rdf.createIRI("http://www.w3.org/ns/anno.jsonld"));
        final String output = out.toString("UTF-8");
        final Graph graph = rdf.createGraph();
        myservice.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, null).forEach(graph::add);
        assertAll("Check compact serialization", checkCompactSerialization(output, graph));
    }

    @Test
    void testJsonLdCustomUnrecognizedSerializer2() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service2.write(getTriples(), out, JSONLD, identifier, rdf.createIRI("http://www.example.org/context.jsonld"));
        final String output = out.toString("UTF-8");
        final Graph graph = rdf.createGraph();
        service2.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, null).forEach(graph::add);
        assertAll("Check compact serialization", checkCompactSerialization(output, graph));
    }

    @Test
    void testJsonLdCustomUnrecognizedSerializer3() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service.write(getTriples(), out, JSONLD, identifier,
                rdf.createIRI("http://www.trellisldp.org/ns/nonexistent.jsonld"));
        final String output = out.toString("UTF-8");
        final Graph graph = rdf.createGraph();
        service.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, null).forEach(graph::add);
        assertAll("Check compact serialization", checkCompactSerialization(output, graph));
    }

    @Test
    void testJsonLdCompactedSerializer() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service.write(getTriples(), out, JSONLD, identifier, compacted);
        final String output = out.toString("UTF-8");
        final Graph graph = rdf.createGraph();
        service.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, null).forEach(graph::add);
        assertAll("Check compact serialization", checkCompactSerialization(output, graph));
    }

    @Test
    void testJsonLdFlattenedSerializer() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service.write(getTriples(), out, JSONLD, identifier, flattened);
        final String output = out.toString("UTF-8");
        final Graph graph = rdf.createGraph();
        service.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, null).forEach(graph::add);
        assertAll("Check flattened serialization", checkFlattenedSerialization(output, graph));
    }

    @Test
    void testJsonLdFlattenedSerializer2() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service.write(getTriples(), out, JSONLD, identifier, flattened, compacted);
        final String output = out.toString("UTF-8");
        final Graph graph = rdf.createGraph();
        service.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, null).forEach(graph::add);
        assertAll("Check flattened serialization", checkFlattenedSerialization(output, graph));
    }

    @Test
    void testJsonLdFlattenedSerializer3() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service.write(getTriples(), out, JSONLD, identifier, flattened, expanded);
        final String output = out.toString("UTF-8");
        final Graph graph = rdf.createGraph();
        service.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, null).forEach(graph::add);
        assertAll("Check flattened serialization", checkFlattenedSerialization(output, graph));
    }

    @Test
    void testJsonLdFlattenedSerializer4() throws UnsupportedEncodingException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service.write(getTriples(), out, JSONLD, identifier, compacted, flattened);
        final String output = out.toString("UTF-8");
        final Graph graph = rdf.createGraph();
        service.read(new ByteArrayInputStream(output.getBytes(UTF_8)), JSONLD, null).forEach(graph::add);
        assertAll("Check flattened serialization", checkCompactSerialization(output, graph));
    }

    @Test
    void testMalformedInput() {
        final ByteArrayInputStream in = new ByteArrayInputStream("<> <ex:test> a Literal\" . ".getBytes(UTF_8));
        assertThrows(TrellisRuntimeException.class, () ->
                service.read(in, TURTLE, null), "No exception on malformed input!");
    }

    @Test
    void testNTriplesSerializer() {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service3.write(getTriples(), out, NTRIPLES, identifier);
        final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
        final org.apache.jena.graph.Graph graph = createDefaultGraph();
        RDFDataMgr.read(graph, in, Lang.NTRIPLES);
        assertTrue(validateGraph(fromJena(graph)), "Failed round-trip for N-Triples!");
    }

    @Test
    void testBufferedSerializer() {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service.write(getTriples(), out, RDFXML, identifier);
        final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
        final org.apache.jena.graph.Graph graph = createDefaultGraph();
        RDFDataMgr.read(graph, in, Lang.RDFXML);
        assertTrue(validateGraph(fromJena(graph)), "Failed round-trip for RDFXML!");
    }

    @Test
    void testTurtleSerializer() {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service.write(getTriples(), out, TURTLE, identifier);
        final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
        final org.apache.jena.graph.Graph graph = createDefaultGraph();
        RDFDataMgr.read(graph, in, Lang.TURTLE);
        assertTrue(validateGraph(fromJena(graph)), "Failed round-trip for Turtle!");
    }

    @Test
    void testTurtleReaderWithContext() {
        final Graph graph = rdf.createGraph();
        service.read(getClass().getResourceAsStream("/testRdf.ttl"), TURTLE, identifier)
            .forEach(graph::add);
        assertTrue(validateGraph(graph), "Failed round-trip for Turtle using a context value!");
    }

    @Test
    void testHtmlSerializer() {
        final IOService service4 = new JenaIOService(mockNamespaceService, mockRdfaWriterService);

        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service4.write(getComplexTriples(), out, RDFA, "http://example.org/");
        verify(mockRdfaWriterService).write(any(), eq(out), eq("http://example.org/"));
    }

    @Test
    void testHtmlSerializer2() {
        final IOService service4 = new JenaIOService(mockNamespaceService, mockRdfaWriterService);

        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service4.write(getComplexTriples(), out, RDFA, null);
        verify(mockRdfaWriterService).write(any(), eq(out), eq(null));
    }

    @Test
    void testNullHtmlSerializer() {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        service.write(getTriples(), out, RDFA, identifier);
        final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
        final org.apache.jena.graph.Graph graph = createDefaultGraph();
        RDFDataMgr.read(graph, in, Lang.TURTLE);
        assertTrue(validateGraph(fromJena(graph)), "null HTML serialization didn't default to Turtle!");
    }

    @Test
    void testUpdateError() {
        final Graph graph = rdf.createGraph();
        getTriples().forEach(graph::add);
        assertEquals(3L, graph.size(), "Incorrect graph size!");
        assertThrows(TrellisRuntimeException.class, () ->
                service.update(graph, "blah blah blah blah blah", SPARQL_UPDATE, null), "no exception on bad update!");
    }

    @Test
    void testReadError() throws IOException {
        doThrow(new IOException()).when(mockInputStream).read(any(byte[].class), anyInt(), anyInt());
        assertThrows(TrellisRuntimeException.class, () -> service.read(mockInputStream, TURTLE, "context"),
                "No read exception on bad input stream!");
    }

    @Test
    void testWriteError() throws IOException {
        doThrow(new IOException()).when(mockOutputStream).write(any(byte[].class), anyInt(), anyInt());
        final Stream<Triple> triples = getTriples();
        assertThrows(TrellisRuntimeException.class, () -> service.write(triples, mockOutputStream, TURTLE,
                    identifier), "No write exception on bad input stream!");
    }

    @Test
    void testUpdate() {
        final Graph graph = rdf.createGraph();
        getTriples().forEach(graph::add);
        assertEquals(3L, graph.size(), "Incorrect graph size!");
        service.update(graph, "DELETE WHERE { ?s <http://purl.org/dc/terms/title> ?o }", SPARQL_UPDATE, "test:info");
        assertEquals(2L, graph.size(), "Incorrect graph size, post update!");
        service.update(graph, "INSERT { " +
                "<> <http://purl.org/dc/terms/title> \"Other title\" } WHERE {}", SPARQL_UPDATE,
                "trellis:data/resource");
        assertEquals(3L, graph.size(), "Incorrect graph size, after adding triple!");
        service.update(graph, "DELETE WHERE { ?s ?p ?o };" +
                "INSERT { <> <http://purl.org/dc/terms/title> \"Other title\" } WHERE {}", SPARQL_UPDATE,
                "trellis:data/");
        assertEquals(1L, graph.size(), "Incorrect graph size after removing triples!");
        assertEquals("<trellis:data/>", graph.stream().findFirst().map(Triple::getSubject)
                .map(RDFTerm::ntriplesString).get(), "Incorrect graph subject from updates!");
    }

    @Test
    void testUpdateInvalidSyntax() {
        final Graph graph = rdf.createGraph();
        getTriples().forEach(graph::add);
        final String patch = "UpdateList <#> <http://example.org/vocab#preferredLanguages> 1..2 ( \"fr\" ) .";
        assertThrows(TrellisRuntimeException.class, () -> service.update(graph, patch, LD_PATCH, null),
                "No exception thrown with invalid update syntax!");
    }

    @Test
    void testWriteInvalidSyntax() {
        when(mockSyntax.mediaType()).thenReturn("fake/mediatype");

        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        final Stream<Triple> triples = getTriples();
        assertThrows(TrellisRuntimeException.class, () -> service.write(triples, out, mockSyntax,
                    identifier), "No exception thrown with invalid write syntax!");
    }

    @Test
    void testReadInvalidSyntax() {
        when(mockSyntax.mediaType()).thenReturn("fake/mediatype");
        final String output = "blah blah blah";

        final InputStream input = new ByteArrayInputStream(output.getBytes(UTF_8));
        assertThrows(TrellisRuntimeException.class, () -> service.read(input, mockSyntax, null),
                "No exception thrown with invalid read syntax!");
    }

    @Test
    void testReadSyntaxes() {
        assertTrue(service.supportedReadSyntaxes().contains(TURTLE), "Turtle not supported for reading!");
        assertTrue(service.supportedReadSyntaxes().contains(JSONLD), "JSON-LD not supported for reading!");
        assertTrue(service.supportedReadSyntaxes().contains(NTRIPLES), "N-Triples not supported for reading!");
        assertFalse(service.supportedReadSyntaxes().contains(RDFXML), "RDF/XML unexpectedly supported for reading!");
        assertFalse(service.supportedReadSyntaxes().contains(RDFA), "RDFa unexpectedly supported for reading!");
        assertTrue(service2.supportedReadSyntaxes().contains(RDFA), "RDFa not supported for reading!");
    }

    @Test
    void testWriteSyntaxes() {
        assertTrue(service.supportedWriteSyntaxes().contains(TURTLE), "Turtle not supported for writing!");
        assertTrue(service.supportedWriteSyntaxes().contains(JSONLD), "JSON-LD not supported for writing!");
        assertTrue(service.supportedWriteSyntaxes().contains(NTRIPLES), "N-Triples not supported for writing!");
        assertFalse(service.supportedWriteSyntaxes().contains(RDFXML), "RDF/XML unexpectedly supported for writing!");
        assertFalse(service.supportedWriteSyntaxes().contains(RDFA), "RDFa unexpectedly supported for writing!");
        assertFalse(service2.supportedWriteSyntaxes().contains(RDFA), "RDFa not supported for writing!");
    }

    @Test
    void testUpdateSyntaxes() {
        assertTrue(service.supportedUpdateSyntaxes().contains(SPARQL_UPDATE), "SPARQL-Update not supported!");
        assertFalse(service.supportedUpdateSyntaxes().contains(LD_PATCH), "LD-PATCH unexpectedly supported!");
    }

    @Test
    void testShouldUseRelativeIRIs() {
        assertTrue(JenaIOService.shouldUseRelativeIRIs(true));
        assertTrue(JenaIOService.shouldUseRelativeIRIs(false, Trellis.SerializationRelative));
        assertTrue(JenaIOService.shouldUseRelativeIRIs(false, Trellis.SerializationRelative,
                    Trellis.SerializationAbsolute));
        assertTrue(JenaIOService.shouldUseRelativeIRIs(true, Trellis.PreferUserManaged));

        assertFalse(JenaIOService.shouldUseRelativeIRIs(false));
        assertFalse(JenaIOService.shouldUseRelativeIRIs(true, Trellis.SerializationAbsolute));
        assertFalse(JenaIOService.shouldUseRelativeIRIs(true, Trellis.SerializationAbsolute,
                    Trellis.SerializationRelative));
        assertFalse(JenaIOService.shouldUseRelativeIRIs(false, Trellis.PreferServerManaged));
    }

    @Test
    void testShouldSkipNamespace() {
        final String foo = "http://example.com/foo";
        final String ns = "http://example.com/namespace#";
        final Set<String> namespaces = singleton(ns);
        assertFalse(JenaIOService.shouldAddNamespace(namespaces, ns, null),
                "Added namespace that already exists");
        assertFalse(JenaIOService.shouldAddNamespace(namespaces, foo, null),
                "Added namespace with null base definition");
        assertFalse(JenaIOService.shouldAddNamespace(namespaces, foo, "http://example.com/bar"),
                "Added namespace with matching scheme/host/port");
        assertFalse(JenaIOService.shouldAddNamespace(namespaces, "not a url", "also not a url"),
                "Added invalid namespace");
        assertTrue(JenaIOService.shouldAddNamespace(namespaces, foo, "http://example.com:80/bar"),
                "Missed namespace with different port definition!");
        assertTrue(JenaIOService.shouldAddNamespace(namespaces, foo, "https://example.com/bar"),
                "Missed namespace with different scheme!");
        assertTrue(JenaIOService.shouldAddNamespace(namespaces, foo, "http://example.com:8080/bar"),
                "Missed namespace with different port definition!");
        assertTrue(JenaIOService.shouldAddNamespace(namespaces, "http://test.example/foo", "http://example.com/bar"),
                "Missed namespace with different host!");
    }

    static Stream<Triple> getTriples() {
        final Node sub = createURI(identifier);
        return of(
                create(sub, title.asNode(), createLiteral("A title")),
                create(sub, spatial.asNode(), createURI("http://sws.geonames.org/4929022/")),
                create(sub, type, Text.asNode()))
            .map(JenaCommonsRDF::fromJena);
    }

    static Stream<Triple> getComplexTriples() {
        final Node sub = createURI(identifier);
        final Node bn = createBlankNode();
        return of(
                create(sub, title.asNode(), createLiteral("A title")),
                create(sub, subject.asNode(), bn),
                create(bn, title.asNode(), createLiteral("Other title")),
                create(sub, spatial.asNode(), createURI("http://sws.geonames.org/4929022/")),
                create(sub, type, Text.asNode()))
            .map(JenaCommonsRDF::fromJena);

    }

    static boolean validateGraph(final Graph graph) {
        return getTriples().map(graph::contains).reduce(true, (acc, x) -> acc && x);
    }

    static Stream<Executable> checkExpandedSerialization(final String serialized, final Graph graph) {
        return of(
                () -> assertTrue(serialized.contains("\"http://purl.org/dc/terms/title\":[{\"@value\":\"A title\"}]"),
                    "no expanded dc:title property!"),
                () -> assertFalse(serialized.contains("\"@context\":"), "unexpected @context!"),
                () -> assertFalse(serialized.contains("\"@graph\":"), "unexpected @graph!"),
                () -> assertTrue(validateGraph(graph), "Not all triples present in output graph!"));
    }

    static Stream<Executable> checkCompactSerialization(final String serialized, final Graph graph) {
        return of(
                () -> assertTrue(serialized.contains("\"title\":\"A title\""), "missing/invalid dc:title value!"),
                () -> assertTrue(serialized.contains("\"@context\":"), "missing @context!"),
                () -> assertFalse(serialized.contains("\"@graph\":"), "unexpected @graph!"),
                () -> assertTrue(validateGraph(graph), "Not all triples present in output graph!"));
    }

    static Stream<Executable> checkFlattenedSerialization(final String serialized, final Graph graph) {
        return of(
                () -> assertTrue(serialized.contains("\"title\":\"A title\""), "missing/invalid dc:title value!"),
                () -> assertTrue(serialized.contains("\"@context\":"), "missing @context!"),
                () -> assertTrue(serialized.contains("\"@graph\":"), "unexpected @graph!"),
                () -> assertTrue(validateGraph(graph), "Not all triples present in output graph!"));
    }
}