001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.fcrepo.http.api.services;
019
020import static org.fcrepo.config.ServerManagedPropsMode.STRICT;
021import static org.junit.Assert.assertEquals;
022import static org.junit.Assert.assertTrue;
023import static org.junit.Assert.assertFalse;
024import static org.mockito.Mockito.when;
025import static org.fcrepo.kernel.api.RdfCollectors.toModel;
026import static org.fcrepo.kernel.api.rdf.DefaultRdfStream.fromModel;
027import static org.slf4j.LoggerFactory.getLogger;
028
029import org.apache.jena.graph.Node;
030import org.apache.jena.graph.NodeFactory;
031import org.apache.jena.rdf.model.ModelFactory;
032import org.apache.jena.rdf.model.ResourceFactory;
033import org.apache.jena.vocabulary.DC;
034import org.apache.jena.vocabulary.DCTerms;
035import javax.ws.rs.core.MediaType;
036import org.apache.jena.rdf.model.Model;
037import org.apache.jena.rdf.model.Resource;
038import org.junit.Before;
039import org.junit.Test;
040import java.io.InputStream;
041import java.io.ByteArrayInputStream;
042import org.junit.runner.RunWith;
043
044import org.fcrepo.config.FedoraPropsConfig;
045import org.fcrepo.http.commons.api.rdf.HttpIdentifierConverter;
046import org.fcrepo.kernel.api.identifiers.FedoraId;
047import org.fcrepo.kernel.api.models.FedoraResource;
048import org.fcrepo.kernel.api.RdfStream;
049import org.mockito.InjectMocks;
050import org.mockito.Mock;
051import org.mockito.junit.MockitoJUnitRunner;
052import org.slf4j.Logger;
053
054/**
055 * Unit tests for HttpRdfService
056 * @author bseeger
057 * @since 2019-11-08
058 */
059@RunWith(MockitoJUnitRunner.Silent.class)
060public class HttpRdfServiceTest {
061
062    private static final Logger log = getLogger(HttpRdfService.class);
063
064    @Mock
065    private HttpIdentifierConverter idTranslator;
066
067    @Mock
068    private FedoraResource resource;
069
070    @Mock
071    private FedoraPropsConfig fedoraPropsConfig;
072
073    @InjectMocks
074    private HttpRdfService httpRdfService;
075
076    private static final String FEDORA_URI_1 = "http://www.example.com/fedora/rest/resource1";
077    private static final Resource FEDORA_URI_1_RESOURCE = ResourceFactory.createResource(FEDORA_URI_1);
078    private static final String FEDORA_URI_2 = "http://www.example.com/fedora/rest/resource2";
079    private static final Resource FEDORA_URI_2_RESOURCE = ResourceFactory.createResource(FEDORA_URI_2);
080    private static final FedoraId FEDORA_ID_1 = FedoraId.create("info:fedora/resource1");
081    private static final Resource FEDORA_ID_1_RESOURCE = ResourceFactory.createResource(FEDORA_ID_1.getFullId());
082    private static final FedoraId FEDORA_ID_2 = FedoraId.create("info:fedora/resource2");
083    private static final Resource FEDORA_ID_2_RESOURCE = ResourceFactory.createResource(FEDORA_ID_2.getFullId());
084    private static final String NON_FEDORA_URI = "http://www.otherdomain.org/resource5";
085    private static final Resource NON_FEDORA_URI_RESOURCE = ResourceFactory.createResource(NON_FEDORA_URI);
086    private static final String RDF =
087                "@prefix dc: <"  + DC.getURI() + "> ." +
088                "@prefix dcterms: <"  + DCTerms.getURI() + "> ." +
089                "<" + FEDORA_URI_1 + "> dc:title 'fancy title' ;" +
090                "    dcterms:isPartOf <" + NON_FEDORA_URI + "> ;" +
091                "    dcterms:isPartOf <" + FEDORA_URI_2 + "> .";
092    private static final String INTERNAL_RDF =
093                "@prefix dc: <"  + DC.getURI() + "> ." +
094                "@prefix dcterms: <"  + DCTerms.getURI() + "> ." +
095                "<" + FEDORA_ID_1 + "> dc:title 'fancy title' ;" +
096                "    dcterms:isPartOf <" + NON_FEDORA_URI + "> ;" +
097                "    dcterms:isPartOf <" + FEDORA_ID_2 + "> .";
098    private static final MediaType CONTENT_TYPE = new MediaType("text", "turtle");
099
100    @Before
101    public void setup() {
102        when(fedoraPropsConfig.getServerManagedPropsMode()).thenReturn(STRICT);
103        when(idTranslator.translateUri(FEDORA_URI_1)).thenReturn(FEDORA_ID_1.getFullId());
104        when(idTranslator.translateUri(FEDORA_URI_2)).thenReturn(FEDORA_ID_2.getFullId());
105        when(idTranslator.translateUri(NON_FEDORA_URI)).thenReturn(NON_FEDORA_URI);
106
107        when(idTranslator.toExternalId(FEDORA_ID_1.getFullId())).thenReturn(FEDORA_URI_1);
108        when(idTranslator.toExternalId(FEDORA_ID_2.getFullId())).thenReturn(FEDORA_URI_2);
109        when(idTranslator.inInternalDomain(FEDORA_ID_1.getFullId())).thenReturn(true);
110        when(idTranslator.inInternalDomain(FEDORA_ID_2.getFullId())).thenReturn(true);
111        when(idTranslator.inInternalDomain(NON_FEDORA_URI)).thenReturn(false);
112
113        when(resource.getId()).thenReturn(FEDORA_ID_1.getFullId());
114
115        when(idTranslator.translateUri("http://other.com/resource")).thenReturn("http://other.com/resource");
116        when(idTranslator.translateUri(FEDORA_ID_1.getFullId())).thenReturn(FEDORA_ID_1.getFullId());
117
118        log.debug("Rdf is: {}", RDF);
119    }
120
121    @Test
122    public void testGetModelFromInputStream() {
123        final InputStream requestBodyStream = new ByteArrayInputStream(RDF.getBytes());
124        final Model model = httpRdfService.bodyToInternalModel(FEDORA_ID_1, requestBodyStream,
125            CONTENT_TYPE, idTranslator, false);
126
127        assertFalse(model.isEmpty());
128
129        verifyTriples(model);
130    }
131
132    @Test
133    public void testConvertInternalToExternalStream() {
134        final InputStream requestBodyStream = new ByteArrayInputStream(INTERNAL_RDF.getBytes());
135        final Node topic = NodeFactory.createURI(FEDORA_ID_1.getEncodedFullId());
136        final Model internalGraph = ModelFactory.createDefaultModel();
137        internalGraph.read(requestBodyStream, FEDORA_ID_1.getEncodedFullId(), "TURTLE");
138        final RdfStream stream = fromModel(topic, internalGraph);
139
140        final RdfStream converted = httpRdfService.bodyToExternalStream(FEDORA_ID_1.getFullId(), stream,
141                idTranslator);
142
143        final Model model = converted.collect(toModel());
144        assertFalse(model.contains(FEDORA_ID_1_RESOURCE, DCTerms.isPartOf, NON_FEDORA_URI_RESOURCE));
145        assertTrue(model.contains(FEDORA_URI_1_RESOURCE, DCTerms.isPartOf, NON_FEDORA_URI_RESOURCE));
146
147        assertFalse(model.contains(FEDORA_ID_1_RESOURCE, DCTerms.isPartOf, FEDORA_ID_2_RESOURCE));
148        assertTrue(model.contains(FEDORA_URI_1_RESOURCE, DCTerms.isPartOf, FEDORA_URI_2_RESOURCE));
149
150        assertFalse(model.containsResource(FEDORA_ID_1_RESOURCE));
151        assertFalse(model.containsResource(FEDORA_ID_2_RESOURCE));
152    }
153
154    @Test
155    public void testPatchToInternal_EmptySubjectUpdate() {
156        final String rdf = "prefix test: <http://example.org#> INSERT { <> test:pointer <http://other.com/resource>" +
157                " } WHERE {}";
158        final String translated = httpRdfService.patchRequestToInternalString(FEDORA_ID_1, rdf, idTranslator);
159        assertStringMatch("<" + FEDORA_ID_1 + "> <http://example.org#pointer> <http://other.com/resource>",
160                translated);
161    }
162
163    @Test
164    public void testPatchToInternal_EmptySubjectInsert() {
165        final String rdf = "prefix test: <http://example.org#> INSERT DATA { <> test:pointer " +
166                "<http://other.com/resource> }";
167        final String translated = httpRdfService.patchRequestToInternalString(FEDORA_ID_1, rdf, idTranslator);
168        assertStringMatch("<" + FEDORA_ID_1 + "> <http://example.org#pointer> <http://other.com/resource>",
169                translated);
170    }
171
172    @Test
173    public void testPatchToInternal_EmptySubjectDelete() {
174        final String rdf = "prefix test: <http://example.org#> DELETE DATA { <> test:pointer " +
175                "<http://other.com/resource> }";
176        final String translated = httpRdfService.patchRequestToInternalString(FEDORA_ID_1, rdf, idTranslator);
177        assertStringMatch("<" + FEDORA_ID_1 + "> <http://example.org#pointer> <http://other.com/resource>",
178                translated);
179    }
180
181    @Test
182    public void testPatchToInternal_ExplicitSubjectUpdate() {
183        final String rdf = "prefix test: <http://example.org#> INSERT { <" + FEDORA_URI_1 + "> test:pointer " +
184                "<http://other.com/resource> } WHERE {}";
185        final String translated = httpRdfService.patchRequestToInternalString(FEDORA_ID_1, rdf, idTranslator);
186        assertStringMatch("<" + FEDORA_ID_1 + "> <http://example.org#pointer> <http://other.com/resource>",
187                translated);
188    }
189
190    @Test
191    public void testPatchToInternal_ExplicitSubjectInsert() {
192        final String rdf = "prefix test: <http://example.org#> INSERT DATA { <" + FEDORA_URI_1 + "> test:pointer " +
193                "<http://other.com/resource> }";
194        final String translated = httpRdfService.patchRequestToInternalString(FEDORA_ID_1, rdf, idTranslator);
195        assertStringMatch("<" + FEDORA_ID_1 + "> <http://example.org#pointer> <http://other.com/resource>",
196                translated);
197    }
198
199    @Test
200    public void testPatchToInternal_ExplicitSubjectDelete() {
201        final String rdf = "prefix test: <http://example.org#> DELETE DATA { <" + FEDORA_URI_1 + "> test:pointer " +
202                "<http://other.com/resource> }";
203        final String translated = httpRdfService.patchRequestToInternalString(FEDORA_ID_1, rdf, idTranslator);
204        assertStringMatch("<" + FEDORA_ID_1 + "> <http://example.org#pointer> <http://other.com/resource>",
205                translated);
206    }
207
208    @Test
209    public void testPatchToInternal_EmptySubjectInsertDeleteWhere() {
210        final String rdf = "prefix test: <http://example.org#> DELETE { <> test:pointer ?o } INSERT { <> test:pointer" +
211                " <http://other.com/resource> } WHERE { <> test:pointer ?o }";
212        final String translated = httpRdfService.patchRequestToInternalString(FEDORA_ID_1, rdf, idTranslator);
213        assertStringMatch("<" + FEDORA_ID_1 + "> <http://example.org#pointer> ?o ", translated);
214        assertStringMatch("<" + FEDORA_ID_1 + "> <http://example.org#pointer> <http://other.com/resource>",
215                translated);
216    }
217
218    @Test
219    public void testPatchToInternal_EmptySubjectObjectInsert() {
220        final String rdf = "prefix test: <http://example.org#> INSERT DATA { <> test:pointer " +
221                "<" + FEDORA_URI_2 + "> }";
222        final String translated = httpRdfService.patchRequestToInternalString(FEDORA_ID_1, rdf, idTranslator);
223        assertStringMatch("<" + FEDORA_ID_1 + "> <http://example.org#pointer> <" + FEDORA_ID_2 + ">",
224                translated);
225    }
226
227    @Test
228    public void testPatchToInternal_ExplicitSubjectObjectInsert() {
229        final String rdf = "prefix test: <http://example.org#> INSERT DATA { <" + FEDORA_URI_1 + "> " +
230                "test:pointer <" + FEDORA_URI_2 + "> }";
231        final String translated = httpRdfService.patchRequestToInternalString(FEDORA_ID_1, rdf, idTranslator);
232        assertStringMatch("<" + FEDORA_ID_1 + "> <http://example.org#pointer> <" + FEDORA_ID_2 + ">",
233                translated);
234    }
235
236    @Test
237    public void testPatchToInternal_EmptySubjectExplicitObjectDelete() {
238        final String rdf = "prefix test: <http://example.org#> DELETE DATA { <> test:pointer <" + FEDORA_URI_2 + "> }";
239        final String translated = httpRdfService.patchRequestToInternalString(FEDORA_ID_1, rdf, idTranslator);
240        assertStringMatch("<" + FEDORA_ID_1 + "> <http://example.org#pointer> <" + FEDORA_ID_2 + ">",
241                translated);
242    }
243
244    @Test
245    public void testPatchToInternal_ExplicitSubjectObjectDelete() {
246        final String rdf = "prefix test: <http://example.org#> DELETE DATA { <" + FEDORA_URI_1 + "> test:pointer <" +
247                FEDORA_URI_2 + "> }";
248        final String translated = httpRdfService.patchRequestToInternalString(FEDORA_ID_1, rdf, idTranslator);
249        assertStringMatch("<" + FEDORA_ID_1 + "> <http://example.org#pointer> <" + FEDORA_ID_2 + ">",
250                translated);
251    }
252
253    @Test
254    public void testPatchToInternal_EmptySubjectExplicitObjectUpdate() {
255        final String rdf = "prefix test: <http://example.org#> DELETE { <> test:pointer ?o } INSERT { <> test:pointer" +
256                " <" + FEDORA_URI_2 + "> } WHERE { <> test:pointer ?o }";
257        final String translated = httpRdfService.patchRequestToInternalString(FEDORA_ID_1, rdf, idTranslator);
258        assertStringMatch("<" + FEDORA_ID_1 + "> <http://example.org#pointer> <" + FEDORA_ID_2 + ">",
259                translated);
260        assertStringMatch("<" + FEDORA_ID_1 + "> <http://example.org#pointer> ?o", translated);
261    }
262
263    @Test
264    public void testPatchToInternal_ExplicitSubjectObjectUpdate() {
265        final String rdf = "prefix test: <http://example.org#> DELETE { <" + FEDORA_URI_2 + "> " +
266                "test:pointer ?o } INSERT { <" + FEDORA_URI_2 + "> test:pointer" +
267                " <" + FEDORA_URI_1 + "> } WHERE { <" + FEDORA_URI_2 + "> test:pointer ?o }";
268        final String translated = httpRdfService.patchRequestToInternalString(FEDORA_ID_2, rdf, idTranslator);
269        assertStringMatch("<" + FEDORA_ID_2 + "> <http://example.org#pointer> <" + FEDORA_ID_1 + ">",
270                translated);
271        assertStringMatch("<" + FEDORA_ID_2 + "> <http://example.org#pointer> ?o", translated);
272    }
273
274    @Test
275    public void testPatchToInternal_ExplicitSubjectObjectUpdateWhereSubject() {
276        final String rdf = "prefix test: <http://example.org#> INSERT { <" + FEDORA_URI_2 + "> test:pointer" +
277                " <" + FEDORA_URI_1 + "> } WHERE { <" + FEDORA_URI_2 + "> test:pointer ?o }";
278        final String translated = httpRdfService.patchRequestToInternalString(FEDORA_ID_2, rdf, idTranslator);
279        assertStringMatch("<" + FEDORA_ID_2 + "> <http://example.org#pointer> " +
280                "<" + FEDORA_ID_1 + ">", translated);
281        assertStringMatch("<" + FEDORA_ID_2 + "> <http://example.org#pointer> ?o", translated);
282    }
283
284    @Test
285    public void testPatchToInternal_ExplicitSubjectObjectUpdateWhereObject() {
286        final String rdf = "prefix test: <http://example.org#> INSERT { <" + FEDORA_URI_2 + "> test:pointer" +
287                " <" + FEDORA_URI_1 + "> } WHERE { ?o test:pointer <" + FEDORA_URI_2 + "> }";
288        final String translated = httpRdfService.patchRequestToInternalString(FEDORA_ID_2, rdf, idTranslator);
289        assertStringMatch("<" + FEDORA_ID_2 + "> <http://example.org#pointer> " +
290                "<" + FEDORA_ID_1 + ">", translated);
291        assertStringMatch("?o <http://example.org#pointer> <" + FEDORA_ID_2 + ">", translated);
292    }
293
294    /**
295     * Assert 2 strings match regardless of whitespace.
296     * @param expected the expected string.
297     * @param comparison the string to test.
298     */
299    private void assertStringMatch(final String expected, final String comparison) {
300        final String cleanedExpected = expected.replaceAll("[\t\n]", "")
301                .replaceAll(" {2,}", " ");
302        final String cleanedTest = comparison.replaceAll("[\t\n]", "")
303                .replaceAll(" {2,}", " ");
304        assertEquals(cleanedExpected, cleanedExpected);
305    }
306
307    private void verifyTriples(final Model model)  {
308
309        final Resource fedoraResource = model.createResource(FEDORA_ID_1.getEncodedFullId());
310
311        // make sure it changed the fedora uri to id, but didn't touch the non fedora domain
312        assertTrue(model.contains(fedoraResource, DCTerms.isPartOf, model.createResource(NON_FEDORA_URI)));
313
314        // make sure it translated the fedora uri to fedora id for this triple in both the subject and object
315        assertTrue(model.contains(fedoraResource, DCTerms.isPartOf,
316                model.createResource(FEDORA_ID_2.getEncodedFullId())));
317
318        // make sure there are no triples that have the subject of the fedora uri
319        assertFalse(model.containsResource(model.createResource(FEDORA_URI_1)));
320    }
321}